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 }