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