kit

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

pkg.c (33189B)


      1 #include <kit/cas.h>
      2 #include <kit/core.h>
      3 #include <kit/package.h>
      4 #include <stddef.h>
      5 #include <stdint.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 
      9 #include "dist_host.h"
     10 #include "driver.h"
     11 #include "env.h"
     12 
     13 #define PKG_TOOL "pkg"
     14 #define PKG_PATH_BUF 1024u
     15 #define PKG_TRUST_LINE_MAX 1024u
     16 #define PKG_KEYID_HEX (2u * KIT_PKG_KEYID_LEN + 1u)
     17 
     18 void driver_help_pkg(void) {
     19   driver_printf(
     20       "kit pkg — create, verify, inspect, and unpack signed packages\n"
     21       "\n"
     22       "USAGE\n"
     23       "  kit pkg keygen -o BASE\n"
     24       "  kit pkg create --name N --version V [--desc D] -s SECKEY\n"
     25       "                   [--format kpkg|tar.gz] [--compression "
     26       "none|lz4-block-v1]\n"
     27       "                   [--native-shape fat|metadata|thin] [--external DIR]\n"
     28       "                   (--cas DIR --tree TREE_ID | --root DIR) -o OUT\n"
     29       "  kit pkg verify [-p PUBKEY | --tofu] [--format kpkg|tar.gz]\n"
     30       "                   [--external DIR] FILE\n"
     31       "  kit pkg unpack [--verify] [-p PUBKEY | --tofu] [--format "
     32       "kpkg|tar.gz]\n"
     33       "                   [--external DIR] FILE -C DIR\n"
     34       "  kit pkg inspect [--manifest | --encoding] FILE\n"
     35       "  kit pkg sign   -s SECKEY [-o OUT] [--comment C] FILE\n"
     36       "  kit pkg verify-signature [-p PUBKEY] [-x SIG] FILE\n"
     37       "  kit pkg trust {path | list | add PUBKEY [label] | remove KEYID}\n"
     38       "\n"
     39       "DESCRIPTION\n"
     40       "  Creates signed native or tar.gz packages, verifies package identity\n"
     41       "  and integrity, inspects manifests, manages trusted keys, and safely\n"
     42       "  unpacks authenticated contents.\n"
     43       "\n"
     44       "OPTIONS\n"
     45       "  -o OUT                  output path for keygen/create/sign\n"
     46       "  -s SECKEY               secret signing key\n"
     47       "  -p PUBKEY               explicit verification key\n"
     48       "  -x SIG                  detached signature (default FILE.minisig)\n"
     49       "  --tofu                  trust on first use\n"
     50       "  --format kpkg|tar.gz    select or force the package format\n"
     51       "  --external DIR          external CAS-shaped object store\n"
     52       "  -C DIR                  unpack destination\n"
     53       "  -h, --help              show this help and exit\n");
     54   driver_printf(
     55       "\n"
     56       "KEYS AND TRUST\n"
     57       "  keygen -o BASE writes BASE.pub (public verification key) and BASE.key\n"
     58       "  (passwordless secret signing key) using the host CSPRNG. Keep .key\n"
     59       "  private. Verification selects trust in this order: -p PUBKEY, --tofu,\n"
     60       "  or the trusted-keys file. --tofu validates the bundled key, then pins\n"
     61       "  it. `pkg trust path` prints the active file; its preferred path is\n"
     62       "  $KIT_TRUSTED_KEYS, otherwise $KIT_HOME/config/trusted_keys. KIT_HOME\n"
     63       "  defaults to $XDG_DATA_HOME/kit (normally ~/.local/share/kit). A legacy\n"
     64       "  $HOME/.config/kit/trusted_keys is also read when the preferred file is\n"
     65       "  absent. `trust list`, `add`, and `remove` manage this state.\n"
     66       "\n"
     67       "CREATE\n"
     68       "  --name N, --version V  required package identity\n"
     69       "  --desc D                optional description\n"
     70       "  -s SECKEY               required BASE.key signing key\n"
     71       "  --root DIR              package a directory as the default tree\n"
     72       "  --cas DIR --tree ID     package an existing CAS tree\n"
     73       "  -o OUT                  required output path\n"
     74       "  --format kpkg|tar.gz    inferred from OUT suffix when omitted\n"
     75       "  --compression none|lz4-block-v1\n"
     76       "                          native chunk compression (default: none)\n"
     77       "  --native-shape fat|metadata|thin\n"
     78       "                          kpkg layout (default: fat). fat embeds all;\n"
     79       "                          metadata embeds trees/index but externalizes\n"
     80       "                          chunks; thin externalizes trees/index/chunks.\n"
     81       "  --external DIR          CAS-shaped objects for non-fat create/verify/\n"
     82       "                          unpack. Kit does not fetch missing objects.\n"
     83       "\n"
     84       "VERIFY, INSPECT, UNPACK\n"
     85       "  verify authenticates the manifest, package layout, trees, and blobs,\n"
     86       "  then prints package/signer identity. inspect prints a summary, the\n"
     87       "  canonical manifest (--manifest), or native encoding (--encoding); it\n"
     88       "  does not establish trust. unpack writes below -C DIR. Use --verify\n"
     89       "  with -p/--tofu/trusted keys to authenticate before materializing.\n"
     90       "  --format may force kpkg or tar.gz; otherwise input magic/suffix is used.\n"
     91       "\n"
     92       "DETACHED SIGNATURES\n"
     93       "  sign writes a stock-minisign-compatible detached signature to OUT, or\n"
     94       "  FILE.minisig when -o is omitted. verify-signature authenticates an\n"
     95       "  arbitrary FILE against -p PUBKEY or the trusted-key store. It never\n"
     96       "  uses TOFU because a detached signature contains no public key.\n"
     97       "\n"
     98       "PATHS\n"
     99       "  Bare -- is not accepted; spell a leading-dash file as ./-name.\n"
    100       "\n"
    101       "EXAMPLES\n"
    102       "  mkdir -p package-root && printf 'hello\\n' > package-root/hello.txt\n"
    103       "  kit pkg keygen -o release-key\n"
    104       "  kit pkg create --name demo --version 1 -s release-key.key \\\n"
    105       "    --root package-root -o demo.kpkg\n"
    106       "  kit pkg verify -p release-key.pub demo.kpkg\n"
    107       "  kit pkg inspect --manifest demo.kpkg\n"
    108       "  kit pkg unpack --verify -p release-key.pub demo.kpkg -C unpacked\n"
    109       "  kit pkg trust add release-key.pub demo-release\n"
    110       "  kit pkg verify demo.kpkg\n"
    111       "  kit pkg sign -s release-key.key release.tar.gz\n"
    112       "  cp demo.kpkg corrupt.kpkg\n"
    113       "  dd if=/dev/zero of=corrupt.kpkg bs=1 seek=100 count=1 conv=notrunc\n"
    114       "  kit pkg verify -p release-key.pub corrupt.kpkg  # exits 1\n"
    115       "\n"
    116       "OUTPUT\n"
    117       "  Results and successful summaries go to stdout; diagnostics go to\n"
    118       "  stderr.\n"
    119       "\n"
    120       "EXIT CODES\n"
    121       "  0   success    1   I/O/format/integrity/trust failure\n"
    122       "  2   bad command-line usage\n");
    123 }
    124 
    125 /* ---------------------------------------------------------------------- */
    126 /* small driver-side helpers                                              */
    127 /* ---------------------------------------------------------------------- */
    128 
    129 static int pkg_read(const KitContext* ctx, const char* path, KitFileData* out) {
    130   out->data = NULL;
    131   out->size = 0;
    132   out->token = NULL;
    133   return ctx->file_io->read_all(ctx->file_io->user, path, out) == KIT_OK;
    134 }
    135 
    136 static void pkg_release(const KitContext* ctx, KitFileData* fd) {
    137   if ((fd->token || fd->data) && ctx->file_io->release)
    138     ctx->file_io->release(ctx->file_io->user, fd);
    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 /* Preferred trusted-keys path under the single-root layout: $KIT_TRUSTED_KEYS,
    158  * else $KIT_HOME/config/trusted_keys (with $KIT_HOME defaulting per
    159  * driver_kit_home). Used for both reads and the tofu/trust writes. */
    160 static int pkg_trust_path(char* buf, size_t cap) {
    161   const char* env = driver_getenv("KIT_TRUSTED_KEYS");
    162   char home[PKG_PATH_BUF];
    163   if (env && *env) {
    164     snprintf(buf, cap, "%s", env);
    165     return 0;
    166   }
    167   if (driver_kit_home(home, sizeof home) != 0) return 1;
    168   snprintf(buf, cap, "%s/config/trusted_keys", home);
    169   return 0;
    170 }
    171 
    172 /* Legacy pre-single-root location, still honored for reads so existing setups
    173  * keep working: $HOME/.config/kit/trusted_keys. Returns 0 on success. */
    174 static int pkg_trust_legacy_path(char* buf, size_t cap) {
    175   const char* home = driver_getenv("HOME");
    176   if (!home || !*home) return 1;
    177   snprintf(buf, cap, "%s/.config/kit/trusted_keys", home);
    178   return 0;
    179 }
    180 
    181 static KitPkgFormat pkg_parse_format(const char* s) {
    182   if (driver_streq(s, "kpkg") || driver_streq(s, "native"))
    183     return KIT_PKG_FORMAT_KPKG;
    184   if (driver_streq(s, "tar.gz") || driver_streq(s, "portable"))
    185     return KIT_PKG_FORMAT_TARGZ;
    186   return KIT_PKG_FORMAT_AUTO;
    187 }
    188 
    189 static KitPkgFormat pkg_infer_format(const char* path) {
    190   if (driver_has_suffix(path, ".tar.gz")) return KIT_PKG_FORMAT_TARGZ;
    191   if (driver_has_suffix(path, ".kpkg")) return KIT_PKG_FORMAT_KPKG;
    192   return KIT_PKG_FORMAT_AUTO;
    193 }
    194 
    195 static int pkg_parse_native_shape(const char* s, KitPkgShape* out) {
    196   if (driver_streq(s, "fat")) {
    197     *out = KIT_PKG_SHAPE_FAT;
    198     return 0;
    199   }
    200   if (driver_streq(s, "metadata") || driver_streq(s, "metadata-rich")) {
    201     *out = KIT_PKG_SHAPE_METADATA;
    202     return 0;
    203   }
    204   if (driver_streq(s, "thin")) {
    205     *out = KIT_PKG_SHAPE_THIN;
    206     return 0;
    207   }
    208   return 1;
    209 }
    210 
    211 static int pkg_parse_compression(const char* s, KitPkgCompression* out) {
    212   if (driver_streq(s, "none")) {
    213     *out = KIT_PKG_COMPRESSION_NONE;
    214     return 0;
    215   }
    216   if (driver_streq(s, "lz4-block-v1") || driver_streq(s, "lz4")) {
    217     *out = KIT_PKG_COMPRESSION_LZ4_BLOCK_V1;
    218     return 0;
    219   }
    220   return 1;
    221 }
    222 
    223 /* ---------------------------------------------------------------------- */
    224 /* keygen                                                                 */
    225 /* ---------------------------------------------------------------------- */
    226 
    227 static int pkg_keygen(DriverEnv* env, const KitContext* ctx, int argc,
    228                       char** argv) {
    229   const char* base = NULL;
    230   KitWriter *pubw = NULL, *seckw = NULL;
    231   uint8_t keyid[KIT_PKG_KEYID_LEN];
    232   char path[PKG_PATH_BUF], hex[PKG_KEYID_HEX];
    233   int i, rc = 1;
    234   (void)env;
    235   for (i = 0; i < argc; ++i) {
    236     if (driver_streq(argv[i], "-o") && i + 1 < argc)
    237       base = argv[++i];
    238     else {
    239       driver_errf(PKG_TOOL, "keygen: unexpected argument: %s", argv[i]);
    240       return 2;
    241     }
    242   }
    243   if (!base) {
    244     driver_errf(PKG_TOOL, "keygen: -o BASE is required");
    245     return 2;
    246   }
    247   snprintf(path, sizeof path, "%s.pub", base);
    248   if (ctx->file_io->open_writer(ctx->file_io->user, path, &pubw) != KIT_OK) {
    249     driver_errf(PKG_TOOL, "keygen: failed to open output: %s", path);
    250     return 1;
    251   }
    252   snprintf(path, sizeof path, "%s.key", base);
    253   if (ctx->file_io->open_writer(ctx->file_io->user, path, &seckw) != KIT_OK) {
    254     driver_errf(PKG_TOOL, "keygen: failed to open output: %s", path);
    255     kit_writer_close(pubw);
    256     return 1;
    257   }
    258   if (kit_pkg_keygen(ctx, driver_dist_random, NULL, pubw, seckw, keyid) ==
    259       KIT_OK)
    260     rc = 0;
    261   kit_writer_close(seckw);
    262   kit_writer_close(pubw);
    263   if (rc == 0) {
    264     kit_hex_encode(hex, keyid, KIT_PKG_KEYID_LEN);
    265     driver_printf("wrote %s.pub and %s.key (key id %s)\n", base, base, hex);
    266   }
    267   return rc;
    268 }
    269 
    270 /* ---------------------------------------------------------------------- */
    271 /* create                                                                 */
    272 /* ---------------------------------------------------------------------- */
    273 
    274 static int pkg_create(DriverEnv* env, const KitContext* ctx, int argc,
    275                       char** argv) {
    276   KitPkgCreateOptions opts;
    277   KitPkgCreateResult result;
    278   KitCasHost host;
    279   KitFileData skfd;
    280   uint8_t sk[KIT_PKG_SK_LEN], keyid[KIT_PKG_KEYID_LEN];
    281   const char* seckey = NULL;
    282   char pkgid_hex[2 * KIT_CAS_HASH_LEN + 1];
    283   int i, rc = 1, sk_loaded = 0;
    284 
    285   memset(&opts, 0, sizeof opts);
    286   opts.format = KIT_PKG_FORMAT_AUTO;
    287   opts.native_shape = KIT_PKG_SHAPE_FAT;
    288   opts.compression = KIT_PKG_COMPRESSION_NONE;
    289   for (i = 0; i < argc; ++i) {
    290     const char* a = argv[i];
    291     if (driver_streq(a, "--name") && i + 1 < argc)
    292       opts.name = argv[++i];
    293     else if (driver_streq(a, "--version") && i + 1 < argc)
    294       opts.version = argv[++i];
    295     else if (driver_streq(a, "--desc") && i + 1 < argc)
    296       opts.description = argv[++i];
    297     else if (driver_streq(a, "-o") && i + 1 < argc)
    298       opts.out_path = argv[++i];
    299     else if (driver_streq(a, "-s") && i + 1 < argc)
    300       seckey = argv[++i];
    301     else if (driver_streq(a, "--cas") && i + 1 < argc)
    302       opts.cas_dir = argv[++i];
    303     else if (driver_streq(a, "--tree") && i + 1 < argc)
    304       opts.tree_id = argv[++i];
    305     else if (driver_streq(a, "--root") && i + 1 < argc)
    306       opts.root_dir = argv[++i];
    307     else if (driver_streq(a, "--external") && i + 1 < argc)
    308       opts.external_dir = argv[++i];
    309     else if (driver_streq(a, "--native-shape") && i + 1 < argc) {
    310       if (pkg_parse_native_shape(argv[++i], &opts.native_shape) != 0) {
    311         driver_errf(PKG_TOOL, "create: unknown native shape");
    312         return 2;
    313       }
    314     } else if (driver_streq(a, "--format") && i + 1 < argc) {
    315       opts.format = pkg_parse_format(argv[++i]);
    316       if (opts.format == KIT_PKG_FORMAT_AUTO) {
    317         driver_errf(PKG_TOOL, "create: unknown format");
    318         return 2;
    319       }
    320     } else if (driver_streq(a, "--compression") && i + 1 < argc) {
    321       if (pkg_parse_compression(argv[++i], &opts.compression) != 0) {
    322         driver_errf(PKG_TOOL, "create: unknown compression");
    323         return 2;
    324       }
    325     } else if (a[0] == '-' && a[1] != '\0') {
    326       driver_errf(PKG_TOOL, "create: unknown option: %s", a);
    327       return 2;
    328     } else {
    329       driver_errf(
    330           PKG_TOOL,
    331           "create: positional file inputs were removed; use --root DIR");
    332       return 2;
    333     }
    334   }
    335   if (!opts.name || !opts.version || !opts.out_path || !seckey) {
    336     driver_errf(PKG_TOOL,
    337                 "create: --name, --version, -s SECKEY and -o OUT are required");
    338     return 2;
    339   }
    340   if ((opts.root_dir != NULL) ==
    341           (opts.cas_dir != NULL || opts.tree_id != NULL) ||
    342       (opts.cas_dir && !opts.tree_id) || (opts.tree_id && !opts.cas_dir)) {
    343     driver_errf(
    344         PKG_TOOL,
    345         "create: pass exactly one of --root DIR or --cas DIR --tree TREE_ID");
    346     return 2;
    347   }
    348   if (opts.format == KIT_PKG_FORMAT_AUTO)
    349     opts.format = pkg_infer_format(opts.out_path);
    350   if (opts.format == KIT_PKG_FORMAT_AUTO) {
    351     driver_errf(PKG_TOOL, "create: cannot infer format; pass --format");
    352     return 2;
    353   }
    354   if (opts.format != KIT_PKG_FORMAT_KPKG &&
    355       opts.native_shape != KIT_PKG_SHAPE_FAT) {
    356     driver_errf(PKG_TOOL, "create: --native-shape only applies to kpkg");
    357     return 2;
    358   }
    359   if (opts.format != KIT_PKG_FORMAT_KPKG && opts.external_dir) {
    360     driver_errf(PKG_TOOL, "create: --external only applies to kpkg");
    361     return 2;
    362   }
    363 
    364   if (!pkg_read(ctx, seckey, &skfd)) {
    365     driver_errf(PKG_TOOL, "create: cannot read secret key: %s", seckey);
    366     return 1;
    367   }
    368   sk_loaded = 1;
    369   {
    370     KitStatus kr = kit_minisig_parse_seckey(skfd.data, skfd.size, sk, keyid);
    371     if (kr == KIT_UNSUPPORTED)
    372       driver_errf(PKG_TOOL, "create: encrypted secret keys need scrypt");
    373     else if (kr != KIT_OK)
    374       driver_errf(PKG_TOOL, "create: malformed secret key: %s", seckey);
    375     if (kr != KIT_OK) goto done;
    376   }
    377   opts.sk = sk;
    378   opts.keyid = keyid;
    379 
    380   host = driver_cas_host(env);
    381   if (kit_pkg_create(ctx, &host, &opts, &result) == KIT_OK) {
    382     kit_hex_encode(pkgid_hex, result.package_id, KIT_CAS_HASH_LEN);
    383     driver_printf("wrote %s (%llu file(s), id %s)\n", opts.out_path,
    384                   (unsigned long long)result.n_files, pkgid_hex);
    385     rc = 0;
    386   }
    387 
    388 done:
    389   if (sk_loaded) pkg_release(ctx, &skfd);
    390   return rc;
    391 }
    392 
    393 /* ---------------------------------------------------------------------- */
    394 /* verify / unpack                                                        */
    395 /* ---------------------------------------------------------------------- */
    396 
    397 /* Persist a trust-on-first-use pin. Returns 0 on success; nonzero if it could
    398  * not be written (no trust path, or I/O failure) — the original tool failed
    399  * verification when the pin could not be persisted, so the caller treats a
    400  * nonzero return as a verify failure. */
    401 static int pkg_pin_tofu(DriverEnv* env, const KitContext* ctx,
    402                         const char* tpath, const KitPkgVerifyResult* r) {
    403   char line[PKG_TRUST_LINE_MAX], parent[PKG_PATH_BUF], hex[PKG_KEYID_HEX];
    404   KitFileData old;
    405   KitWriter* w = NULL;
    406   int had_old, ok = 1, wrote = 0;
    407   if (!tpath[0]) return 1;
    408   kit_hex_encode(hex, r->keyid, KIT_PKG_KEYID_LEN);
    409   if (kit_trust_format_entry(line, sizeof line, r->keyid, r->tofu_pk,
    410                              "tofu-pinned") != KIT_OK)
    411     return 1;
    412   had_old = pkg_read(ctx, tpath, &old);
    413   pkg_parent_dir(tpath, parent, sizeof parent);
    414   if (parent[0]) driver_mkdir_p(env, parent);
    415   if (ctx->file_io->open_writer(ctx->file_io->user, tpath, &w) == KIT_OK) {
    416     if (had_old && old.size)
    417       ok = kit_writer_write(w, old.data, old.size) == KIT_OK;
    418     if (ok) ok = kit_writer_write(w, line, strlen(line)) == KIT_OK;
    419     if (ok && kit_writer_status(w) == KIT_OK) wrote = 1;
    420     kit_writer_close(w);
    421   }
    422   if (had_old) pkg_release(ctx, &old);
    423   if (wrote) driver_printf("pkg: tofu-pinned key id %s to %s\n", hex, tpath);
    424   return wrote ? 0 : 1;
    425 }
    426 
    427 static int pkg_verify_or_unpack(DriverEnv* env, const KitContext* ctx, int argc,
    428                                 char** argv, int unpack) {
    429   const char *file = NULL, *pubkey = NULL, *dir = ".", *external_dir = NULL;
    430   int tofu = 0, explicit_verify = 0, i, rc = 1;
    431   KitPkgFormat fmt = KIT_PKG_FORMAT_AUTO;
    432   KitPkgVerifyOptions opts;
    433   KitPkgVerifyResult result;
    434   KitCasHost host;
    435   KitFileData pkgfd, pubfd, trustfd;
    436   char tpath[PKG_PATH_BUF];
    437   int pub_loaded = 0, trust_loaded = 0, have_tpath;
    438   for (i = 0; i < argc; ++i) {
    439     if (driver_streq(argv[i], "-p") && i + 1 < argc)
    440       pubkey = argv[++i];
    441     else if (driver_streq(argv[i], "--tofu"))
    442       tofu = 1;
    443     else if (unpack && driver_streq(argv[i], "--verify"))
    444       explicit_verify = 1;
    445     else if (driver_streq(argv[i], "--external") && i + 1 < argc)
    446       external_dir = argv[++i];
    447     else if (driver_streq(argv[i], "--format") && i + 1 < argc) {
    448       fmt = pkg_parse_format(argv[++i]);
    449       if (fmt == KIT_PKG_FORMAT_AUTO) {
    450         driver_errf(PKG_TOOL, "%s: unknown format",
    451                     unpack ? "unpack" : "verify");
    452         return 2;
    453       }
    454     } else if (unpack && driver_streq(argv[i], "-C") && i + 1 < argc)
    455       dir = argv[++i];
    456     else if (argv[i][0] != '-')
    457       file = argv[i];
    458     else {
    459       driver_errf(PKG_TOOL, "%s: unknown option: %s",
    460                   unpack ? "unpack" : "verify", argv[i]);
    461       return 2;
    462     }
    463   }
    464   if (!file) {
    465     driver_errf(PKG_TOOL, "%s: FILE is required", unpack ? "unpack" : "verify");
    466     return 2;
    467   }
    468   if (fmt == KIT_PKG_FORMAT_AUTO) fmt = pkg_infer_format(file);
    469 
    470   if (!pkg_read(ctx, file, &pkgfd)) {
    471     driver_errf(PKG_TOOL, "cannot read package: %s", file);
    472     return 1;
    473   }
    474   memset(&opts, 0, sizeof opts);
    475   opts.pkg_data = pkgfd.data;
    476   opts.pkg_len = pkgfd.size;
    477   opts.format = fmt;
    478   opts.external_dir = external_dir;
    479   opts.unpack_dir = unpack ? dir : NULL;
    480   opts.tofu = tofu;
    481   if (pubkey) {
    482     if (!pkg_read(ctx, pubkey, &pubfd)) {
    483       driver_errf(PKG_TOOL, "cannot read public key: %s", pubkey);
    484       pkg_release(ctx, &pkgfd);
    485       return 1;
    486     }
    487     pub_loaded = 1;
    488     opts.pubkey_bytes = pubfd.data;
    489     opts.pubkey_len = pubfd.size;
    490   }
    491   have_tpath = (pkg_trust_path(tpath, sizeof tpath) == 0);
    492   if (!have_tpath) tpath[0] = '\0';
    493   if (!pubkey) {
    494     char legacy[PKG_PATH_BUF];
    495     if (have_tpath && pkg_read(ctx, tpath, &trustfd))
    496       trust_loaded = 1;
    497     else if (pkg_trust_legacy_path(legacy, sizeof legacy) == 0 &&
    498              pkg_read(ctx, legacy, &trustfd))
    499       trust_loaded = 1; /* fall back to the pre-single-root location */
    500     if (trust_loaded) {
    501       opts.trusted_keys = trustfd.data;
    502       opts.trusted_keys_len = trustfd.size;
    503     }
    504   }
    505 
    506   host = driver_cas_host(env);
    507   if (kit_pkg_verify(ctx, &host, &opts, &result) == KIT_OK) {
    508     /* A trust-on-first-use acceptance must persist its pin, like the original
    509      * tool — a pin that can't be written fails verification (exit 1). */
    510     if (result.tofu_pin && pkg_pin_tofu(env, ctx, tpath, &result) != 0) {
    511       driver_errf(PKG_TOOL,
    512                   "tofu: could not persist key id to trusted-keys "
    513                   "(set KIT_TRUSTED_KEYS or HOME)");
    514     } else {
    515       int quiet = unpack && !explicit_verify;
    516       if (!quiet) {
    517         char idhex[PKG_KEYID_HEX];
    518         kit_hex_encode(idhex, result.keyid, KIT_PKG_KEYID_LEN);
    519         driver_printf("ok: %s %s  signer %s  [%s]\n", result.name,
    520                       result.version, idhex, result.trusted);
    521       }
    522       if (opts.unpack_dir)
    523         driver_printf("unpacked %s %s to %s\n", result.name, result.version,
    524                       opts.unpack_dir);
    525       rc = 0;
    526     }
    527   }
    528 
    529   if (trust_loaded) pkg_release(ctx, &trustfd);
    530   if (pub_loaded) pkg_release(ctx, &pubfd);
    531   pkg_release(ctx, &pkgfd);
    532   return rc;
    533 }
    534 
    535 /* ---------------------------------------------------------------------- */
    536 /* inspect                                                                */
    537 /* ---------------------------------------------------------------------- */
    538 
    539 static int pkg_inspect(DriverEnv* env, const KitContext* ctx, int argc,
    540                        char** argv) {
    541   const char* file = NULL;
    542   KitPkgFormat fmt = KIT_PKG_FORMAT_AUTO;
    543   int show_encoding = 0, i, rc = 1;
    544   KitFileData fd;
    545   KitWriter* out;
    546   for (i = 0; i < argc; ++i) {
    547     if (driver_streq(argv[i], "--format") && i + 1 < argc) {
    548       fmt = pkg_parse_format(argv[++i]);
    549       if (fmt == KIT_PKG_FORMAT_AUTO) {
    550         driver_errf(PKG_TOOL, "inspect: unknown format");
    551         return 2;
    552       }
    553     } else if (driver_streq(argv[i], "--manifest")) {
    554       show_encoding = 0;
    555     } else if (driver_streq(argv[i], "--encoding")) {
    556       show_encoding = 1;
    557     } else if (argv[i][0] != '-')
    558       file = argv[i];
    559     else {
    560       driver_errf(PKG_TOOL, "inspect: unknown option: %s", argv[i]);
    561       return 2;
    562     }
    563   }
    564   if (!file) {
    565     driver_errf(PKG_TOOL, "inspect: FILE is required");
    566     return 2;
    567   }
    568   if (fmt == KIT_PKG_FORMAT_AUTO) fmt = pkg_infer_format(file);
    569   if (fmt == KIT_PKG_FORMAT_TARGZ && show_encoding) {
    570     driver_errf(PKG_TOOL,
    571                 "inspect: portable packages have no encoding descriptor");
    572     return 2;
    573   }
    574   if (!pkg_read(ctx, file, &fd)) {
    575     driver_errf(PKG_TOOL, "cannot read package: %s", file);
    576     return 1;
    577   }
    578   out = driver_stdout_writer(env);
    579   if (out) {
    580     if (kit_pkg_inspect(ctx, fd.data, fd.size, fmt, show_encoding, out) ==
    581         KIT_OK)
    582       rc = 0;
    583     kit_writer_close(out);
    584   }
    585   pkg_release(ctx, &fd);
    586   return rc;
    587 }
    588 
    589 /* ---------------------------------------------------------------------- */
    590 /* trust                                                                  */
    591 /* ---------------------------------------------------------------------- */
    592 
    593 static int pkg_trust(DriverEnv* env, const KitContext* ctx, int argc,
    594                      char** argv) {
    595   char tpath[PKG_PATH_BUF];
    596   const char* sub = (argc > 0) ? argv[0] : "list";
    597   if (pkg_trust_path(tpath, sizeof tpath) != 0) {
    598     driver_errf(PKG_TOOL,
    599                 "no trusted-keys path (set KIT_TRUSTED_KEYS or HOME)");
    600     return 1;
    601   }
    602   if (driver_streq(sub, "path")) {
    603     if (argc > 1) {
    604       driver_errf(PKG_TOOL, "trust path: unexpected argument: %s", argv[1]);
    605       return 2;
    606     }
    607     driver_printf("%s\n", tpath);
    608     return 0;
    609   }
    610   if (driver_streq(sub, "list")) {
    611     KitFileData fd;
    612     if (!pkg_read(ctx, tpath, &fd)) {
    613       driver_printf("(no trusted keys at %s)\n", tpath);
    614       return 0;
    615     }
    616     driver_printf("%.*s", (int)fd.size, (const char*)fd.data);
    617     pkg_release(ctx, &fd);
    618     return 0;
    619   }
    620   if (driver_streq(sub, "add")) {
    621     const char* pubkey = (argc > 1) ? argv[1] : NULL;
    622     const char* label = (argc > 2) ? argv[2] : "";
    623     KitFileData kf, old;
    624     uint8_t pk[KIT_PKG_PK_LEN], keyid[KIT_PKG_KEYID_LEN], dummy[KIT_PKG_PK_LEN];
    625     char line[PKG_TRUST_LINE_MAX], parent[PKG_PATH_BUF];
    626     int had_old, ok = 1, rc = 1;
    627     KitWriter* w = NULL;
    628     if (!pubkey) {
    629       driver_errf(PKG_TOOL, "trust add: PUBKEY is required");
    630       return 2;
    631     }
    632     if (!pkg_read(ctx, pubkey, &kf)) {
    633       driver_errf(PKG_TOOL, "cannot read public key: %s", pubkey);
    634       return 1;
    635     }
    636     ok = kit_minisig_parse_pubkey(kf.data, kf.size, pk, keyid) == KIT_OK;
    637     pkg_release(ctx, &kf);
    638     if (!ok) return 1;
    639     had_old = pkg_read(ctx, tpath, &old);
    640     if (had_old &&
    641         kit_trust_lookup(old.data, old.size, keyid, dummy) == KIT_OK) {
    642       pkg_release(ctx, &old);
    643       return 0;
    644     }
    645     if (kit_trust_format_entry(line, sizeof line, keyid, pk, label) != KIT_OK) {
    646       if (had_old) pkg_release(ctx, &old);
    647       return 1;
    648     }
    649     pkg_parent_dir(tpath, parent, sizeof parent);
    650     if (parent[0]) driver_mkdir_p(env, parent);
    651     if (ctx->file_io->open_writer(ctx->file_io->user, tpath, &w) == KIT_OK) {
    652       if (had_old && old.size)
    653         ok = kit_writer_write(w, old.data, old.size) == KIT_OK;
    654       if (ok) ok = kit_writer_write(w, line, strlen(line)) == KIT_OK;
    655       if (ok && kit_writer_status(w) == KIT_OK) rc = 0;
    656       kit_writer_close(w);
    657     }
    658     if (had_old) pkg_release(ctx, &old);
    659     return rc;
    660   }
    661   if (driver_streq(sub, "remove")) {
    662     const char* idhex = (argc > 1) ? argv[1] : NULL;
    663     KitFileData old;
    664     uint8_t want[KIT_PKG_KEYID_LEN];
    665     KitWriter* w = NULL;
    666     size_t pos = 0;
    667     int rc = 1;
    668     if (!idhex || strlen(idhex) != 2 * KIT_PKG_KEYID_LEN ||
    669         kit_hex_decode(want, idhex, KIT_PKG_KEYID_LEN) != KIT_OK)
    670       return 2;
    671     if (!pkg_read(ctx, tpath, &old)) return 0;
    672     if (ctx->file_io->open_writer(ctx->file_io->user, tpath, &w) != KIT_OK) {
    673       pkg_release(ctx, &old);
    674       return 1;
    675     }
    676     rc = 0;
    677     while (pos < old.size) {
    678       size_t start = pos, end = pos;
    679       uint8_t got[KIT_PKG_KEYID_LEN];
    680       char idbuf[2 * KIT_PKG_KEYID_LEN + 1];
    681       int keep = 1;
    682       while (end < old.size && old.data[end] != '\n') ++end;
    683       if (end - start >= 2 * KIT_PKG_KEYID_LEN) {
    684         memcpy(idbuf, old.data + start, 2 * KIT_PKG_KEYID_LEN);
    685         idbuf[2 * KIT_PKG_KEYID_LEN] = '\0';
    686         if (kit_hex_decode(got, idbuf, KIT_PKG_KEYID_LEN) == KIT_OK &&
    687             memcmp(got, want, KIT_PKG_KEYID_LEN) == 0)
    688           keep = 0;
    689       }
    690       if (keep) {
    691         size_t n = (end < old.size ? end + 1 : end) - start;
    692         if (kit_writer_write(w, old.data + start, n) != KIT_OK) rc = 1;
    693       }
    694       pos = (end < old.size) ? end + 1 : end;
    695     }
    696     if (kit_writer_status(w) != KIT_OK) rc = 1;
    697     kit_writer_close(w);
    698     pkg_release(ctx, &old);
    699     return rc;
    700   }
    701   driver_errf(PKG_TOOL, "trust: unknown subcommand: %s", sub);
    702   return 2;
    703 }
    704 
    705 /* ---------------------------------------------------------------------- */
    706 /* verify-signature: detached minisign verification                       */
    707 /* ---------------------------------------------------------------------- */
    708 
    709 static int pkg_verify_signature(DriverEnv* env, const KitContext* ctx,
    710                                 int argc, char** argv) {
    711   const char *file = NULL, *pubkey = NULL, *sig_path = NULL;
    712   char sig_buf[PKG_PATH_BUF], tpath[PKG_PATH_BUF];
    713   KitFileData datafd, sigfd, pubfd, trustfd;
    714   KitPkgDetachedVerifyOptions opts;
    715   KitPkgDetachedVerifyResult result;
    716   int data_loaded = 0, sig_loaded = 0, pub_loaded = 0, trust_loaded = 0;
    717   int i, rc = 1;
    718   (void)env;
    719 
    720   for (i = 0; i < argc; ++i) {
    721     if (driver_streq(argv[i], "-p") && i + 1 < argc) {
    722       pubkey = argv[++i];
    723     } else if (driver_streq(argv[i], "-x") && i + 1 < argc) {
    724       sig_path = argv[++i];
    725     } else if (argv[i][0] != '-') {
    726       if (file) {
    727         driver_errf(PKG_TOOL, "verify-signature: unexpected argument: %s",
    728                     argv[i]);
    729         return 2;
    730       }
    731       file = argv[i];
    732     } else {
    733       driver_errf(PKG_TOOL, "verify-signature: unknown option: %s", argv[i]);
    734       return 2;
    735     }
    736   }
    737   if (!file) {
    738     driver_errf(PKG_TOOL, "verify-signature: FILE is required");
    739     return 2;
    740   }
    741   if (!sig_path) {
    742     int n = snprintf(sig_buf, sizeof sig_buf, "%s.minisig", file);
    743     if (n < 0 || (size_t)n >= sizeof sig_buf) {
    744       driver_errf(PKG_TOOL, "verify-signature: signature path is too long");
    745       return 1;
    746     }
    747     sig_path = sig_buf;
    748   }
    749 
    750   memset(&opts, 0, sizeof opts);
    751   if (!pkg_read(ctx, file, &datafd)) {
    752     driver_errf(PKG_TOOL, "verify-signature: cannot read file: %s", file);
    753     goto done;
    754   }
    755   data_loaded = 1;
    756   opts.data = datafd.data;
    757   opts.data_len = datafd.size;
    758   if (!pkg_read(ctx, sig_path, &sigfd)) {
    759     driver_errf(PKG_TOOL, "verify-signature: cannot read signature: %s",
    760                 sig_path);
    761     goto done;
    762   }
    763   sig_loaded = 1;
    764   opts.signature = sigfd.data;
    765   opts.signature_len = sigfd.size;
    766 
    767   if (pubkey) {
    768     if (!pkg_read(ctx, pubkey, &pubfd)) {
    769       driver_errf(PKG_TOOL, "verify-signature: cannot read public key: %s",
    770                   pubkey);
    771       goto done;
    772     }
    773     pub_loaded = 1;
    774     opts.pubkey_bytes = pubfd.data;
    775     opts.pubkey_len = pubfd.size;
    776   } else {
    777     char legacy[PKG_PATH_BUF];
    778     if (pkg_trust_path(tpath, sizeof tpath) == 0 &&
    779         pkg_read(ctx, tpath, &trustfd)) {
    780       trust_loaded = 1;
    781     } else if (pkg_trust_legacy_path(legacy, sizeof legacy) == 0 &&
    782                pkg_read(ctx, legacy, &trustfd)) {
    783       trust_loaded = 1;
    784     }
    785     if (trust_loaded) {
    786       opts.trusted_keys = trustfd.data;
    787       opts.trusted_keys_len = trustfd.size;
    788     }
    789   }
    790 
    791   if (kit_pkg_verify_detached(ctx, &opts, &result) == KIT_OK) {
    792     char idhex[PKG_KEYID_HEX];
    793     kit_hex_encode(idhex, result.keyid, KIT_PKG_KEYID_LEN);
    794     driver_printf("ok: %s  signer %s  [%s]\n", file, idhex, result.trusted);
    795     rc = 0;
    796   }
    797 
    798 done:
    799   if (trust_loaded) pkg_release(ctx, &trustfd);
    800   if (pub_loaded) pkg_release(ctx, &pubfd);
    801   if (sig_loaded) pkg_release(ctx, &sigfd);
    802   if (data_loaded) pkg_release(ctx, &datafd);
    803   return rc;
    804 }
    805 
    806 /* ---------------------------------------------------------------------- */
    807 /* sign: detached minisign signature over a file                          */
    808 /* ---------------------------------------------------------------------- */
    809 
    810 static int pkg_sign(DriverEnv* env, const KitContext* ctx, int argc,
    811                     char** argv) {
    812   const char *file = NULL, *seckey = NULL, *out_path = NULL, *comment = NULL;
    813   KitFileData msgfd, skfd;
    814   KitWriter* w = NULL;
    815   char out_buf[PKG_PATH_BUF];
    816   int msg_loaded = 0, sk_loaded = 0, i, rc = 1;
    817   KitStatus st;
    818   (void)env;
    819   for (i = 0; i < argc; ++i) {
    820     if (driver_streq(argv[i], "-s") && i + 1 < argc)
    821       seckey = argv[++i];
    822     else if (driver_streq(argv[i], "-o") && i + 1 < argc)
    823       out_path = argv[++i];
    824     else if (driver_streq(argv[i], "--comment") && i + 1 < argc)
    825       comment = argv[++i];
    826     else if (argv[i][0] != '-')
    827       file = argv[i];
    828     else {
    829       driver_errf(PKG_TOOL, "sign: unknown option: %s", argv[i]);
    830       return 2;
    831     }
    832   }
    833   if (!file || !seckey) {
    834     driver_errf(PKG_TOOL, "sign: FILE and -s SECKEY are required");
    835     return 2;
    836   }
    837   if (!out_path) {
    838     snprintf(out_buf, sizeof out_buf, "%s.minisig", file);
    839     out_path = out_buf;
    840   }
    841   if (!pkg_read(ctx, file, &msgfd)) {
    842     driver_errf(PKG_TOOL, "sign: cannot read file: %s", file);
    843     return 1;
    844   }
    845   msg_loaded = 1;
    846   if (!pkg_read(ctx, seckey, &skfd)) {
    847     driver_errf(PKG_TOOL, "sign: cannot read secret key: %s", seckey);
    848     goto done;
    849   }
    850   sk_loaded = 1;
    851   if (ctx->file_io->open_writer(ctx->file_io->user, out_path, &w) != KIT_OK) {
    852     driver_errf(PKG_TOOL, "sign: cannot open output: %s", out_path);
    853     goto done;
    854   }
    855   st = kit_pkg_sign_detached(ctx, msgfd.data, msgfd.size, skfd.data, skfd.size,
    856                              comment, w);
    857   if (st != KIT_OK) driver_writer_abort(w);
    858   kit_writer_close(w);
    859   if (st == KIT_UNSUPPORTED)
    860     driver_errf(PKG_TOOL, "sign: encrypted secret keys need scrypt");
    861   else if (st != KIT_OK)
    862     driver_errf(PKG_TOOL, "sign: failed to sign %s", file);
    863   else {
    864     driver_printf("wrote %s\n", out_path);
    865     rc = 0;
    866   }
    867 
    868 done:
    869   if (sk_loaded) pkg_release(ctx, &skfd);
    870   if (msg_loaded) pkg_release(ctx, &msgfd);
    871   return rc;
    872 }
    873 
    874 int driver_pkg(int argc, char** argv) {
    875   DriverEnv env;
    876   KitContext ctx;
    877   const char* sub;
    878   int rc;
    879   if (driver_argv_wants_help(argc, argv, 1) || argc < 2) {
    880     driver_help_pkg();
    881     return 0;
    882   }
    883   sub = argv[1];
    884   driver_env_init(&env);
    885   ctx = driver_env_to_context(&env);
    886   if (driver_streq(sub, "keygen"))
    887     rc = pkg_keygen(&env, &ctx, argc - 2, argv + 2);
    888   else if (driver_streq(sub, "create"))
    889     rc = pkg_create(&env, &ctx, argc - 2, argv + 2);
    890   else if (driver_streq(sub, "verify"))
    891     rc = pkg_verify_or_unpack(&env, &ctx, argc - 2, argv + 2, 0);
    892   else if (driver_streq(sub, "unpack"))
    893     rc = pkg_verify_or_unpack(&env, &ctx, argc - 2, argv + 2, 1);
    894   else if (driver_streq(sub, "inspect"))
    895     rc = pkg_inspect(&env, &ctx, argc - 2, argv + 2);
    896   else if (driver_streq(sub, "sign"))
    897     rc = pkg_sign(&env, &ctx, argc - 2, argv + 2);
    898   else if (driver_streq(sub, "verify-signature"))
    899     rc = pkg_verify_signature(&env, &ctx, argc - 2, argv + 2);
    900   else if (driver_streq(sub, "trust"))
    901     rc = pkg_trust(&env, &ctx, argc - 2, argv + 2);
    902   else {
    903     driver_errf(PKG_TOOL, "unknown subcommand: %s", sub);
    904     rc = 2;
    905   }
    906   driver_env_fini(&env);
    907   return rc;
    908 }