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