/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. Licensed under the StarForth License, Version 1.0 */ #ifndef __STARKERNEL__ #error "capsule_wirebind.c is kernel-only" #endif #include "starkernel/capsule_wirebind.h" #include "starkernel/capsule_birth.h" #include "starkernel/capsule_runcap.h" #include "starkernel/capsule_console.h" #include "starkernel/vm_identity.h" #include "starkernel/user_identity_seed.h" #include "starkernel/console.h" #include "blkio.h" #include "block_subsystem.h" /* compute_crc64() */ #include "freestanding/stdio.h" #include #include /* WIREBIND_CERT_MAX_DEVBLOCKS: a sane upper bound on how much cert * content this reads, independent of whatever sig->cert_devblocks * claims -- MINT (capsule_mint.c) only ever writes 1 devblock's worth * (a real cert is ~150-250 bytes), so 4 devblocks (16KB) is already * generous headroom, not a real constraint. */ #define WIREBIND_CERT_MAX_DEVBLOCKS 4u /* Read exactly one devblock (4096 bytes) at devblock offset `devblock` * -- same convention capsule_runcap.c/capsule_mint.c already use. */ static int read_devblock(struct blkio_dev *dev, uint32_t devblock, uint8_t *buf4096) { uint32_t base = devblock * 4u; for (uint32_t i = 0; i < 4u; i++) { if (blkio_read((blkio_dev_t *)dev, base + i, buf4096 + (size_t)i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK) { return -1; } } return 0; } int capsule_wirebind_verify_cert(struct blkio_dev *dev, const homeblocks_sig_t *sig, VM *mama_vm, VMIdentity *out) { if (!dev || !sig || !mama_vm || !out) return -1; /* Genesis-mode Zuse drives have no cert region -- capsule_zuse_boot_ * try_attach() owns that case, not this one. */ if (sig->cert_offset == 0 || sig->cert_devblocks == 0) return -1; /* Nothing to verify a regular cert against until Zuse herself has * authenticated this boot. */ if (!mama_vm->zuse_cert_installed) return -1; /* Read the cert region into a local buffer. */ uint32_t n_devblocks = sig->cert_devblocks; if (n_devblocks > WIREBIND_CERT_MAX_DEVBLOCKS) n_devblocks = WIREBIND_CERT_MAX_DEVBLOCKS; uint8_t cert_der[WIREBIND_CERT_MAX_DEVBLOCKS * 4096]; for (uint32_t d = 0; d < n_devblocks; d++) { if (read_devblock(dev, sig->cert_offset + d, cert_der + (size_t)d * 4096) != 0) { return -1; } } size_t cert_len = (size_t)n_devblocks * 4096; memset(out, 0, sizeof(*out)); return vm_identity_from_cert(out, cert_der, cert_len, mama_vm->zuse_cert_pubkey, sig->drive_uuid, 0 /* acl_caps: no bits assigned yet, §F.2 */); } void capsule_wirebind_try_attach(struct blkio_dev *dev, const homeblocks_sig_t *sig, VM *mama_vm) { if (!dev || !sig || !mama_vm) return; /* Genesis-mode Zuse drives have no cert region -- capsule_zuse_boot_ * try_attach() already owns that case. Check here too so the right * "not applicable" case stays silent (matches capsule_wirebind_ * verify_cert()'s own -1, but avoids a misleading refusal message * for a drive this function was never going to act on anyway). */ if (sig->cert_offset == 0 || sig->cert_devblocks == 0) return; if (!mama_vm->zuse_cert_installed) return; VMIdentity identity; if (capsule_wirebind_verify_cert(dev, sig, mama_vm, &identity) != 0) { console_println("WIREBIND: cert verification FAILED -- drive refused"); return; } /* Verified. Read the drive's own profile for its username. */ if (sig->identity_src_offset == 0 || sig->identity_src_devblocks < 1) { console_println("WIREBIND: verified cert but no identity_src region -- refusing"); return; } user_identity_seed_t idrec; if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) { console_println("WIREBIND: verified cert but identity record unreadable -- refusing"); return; } /* Same magic -> version -> CRC-64 discipline as capsule_zuse_boot.c: * the identity record is the same on-disk format, so a corrupt or * format-mismatched record must be refused rather than trusted. */ if (idrec.magic != USER_IDENTITY_SEED_MAGIC || idrec.version != USER_IDENTITY_SEED_VERSION) { console_println("WIREBIND: verified cert but identity record unreadable -- refusing"); return; } uint64_t want_crc = compute_crc64((const uint8_t *)&idrec, offsetof(user_identity_seed_t, crc)); if (want_crc != idrec.crc) { console_println("WIREBIND: verified cert but identity record corrupt -- refusing"); return; } char username[USER_IDENTITY_USERNAME_MAX]; memcpy(username, idrec.username, sizeof(username)); username[sizeof(username) - 1] = '\0'; /* defensive; MINT already NUL-terminates */ char user_vm_name[USER_IDENTITY_USERNAME_MAX + 8]; size_t ulen = strlen(username); if (ulen == 0 || ulen + 6 > sizeof(user_vm_name)) { console_println("WIREBIND: empty or oversized username -- refusing"); return; } memcpy(user_vm_name, username, ulen); memcpy(user_vm_name + ulen, "~user", 6); /* Idempotent: already bound this session (console VM already live * under this username) -- nothing to do. Matches BIRTH's own * idempotent convention. */ { VMRegistryEntry existing; if (capsule_vm_find_by_name(username, &existing) == 0 && existing.state == VM_STATE_LIVE) { return; } } VMUuid console_id, user_id; void *console_ctx = (void *)0; void *user_ctx = (void *)0; if (capsule_console_birth(username, mama_vm->stadium_vm_id, &console_id, &console_ctx) != CAPSULE_RUN_OK) { console_println("WIREBIND: console VM birth FAILED"); return; } if (capsule_runcap_birth(dev, sig, user_vm_name, mama_vm->stadium_vm_id, &user_id, &user_ctx) != CAPSULE_RUN_OK) { console_println("WIREBIND: user VM birth FAILED"); return; } /* Install the verified identity onto the user VM -- the one BINDSTEP * (§F.9) will later re-verify against on every USE. */ if (user_ctx) { ((VM *)user_ctx)->identity = identity; } /* Register the pairing in the console's own routing table, index 3 * -- the fixed convention sk_repl_dispatch_line() (repl.c) uses. */ { char reg_cmd[sizeof(user_vm_name) + 32]; int n = snprintf(reg_cmd, sizeof(reg_cmd), "S\" %s\" 3 VM-NAME-REG", user_vm_name); if (n > 0 && (size_t)n < sizeof(reg_cmd)) { vm_interpret((VM *)console_ctx, reg_cmd); } } console_puts("WIREBIND: "); console_puts(username); console_println(" attached and ready -- USE it to begin"); }