/* sk_v4_identity.c -- the identity gate for the v4 boot: no identity drive, * headless; Zuse's minted thumbdrive, a console. * docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md, ENGINE.md 2. * * As v3 (kernel_main.c, repl.c's sk_repl_headless_wait()/sk_repl_idle(), * capsule_zuse_boot.c), done by the kernel with no v3 VM behind it: Artemis * and Hera are the kernel's (ENGINE.md 2, Q1), so a drive that arrives is * given to the block subsystem here, and Zuse's session is kept here, for * the first node, Hera. The check that the drive is hers is v3's own * (capsule_zuse_boot_check_key(), against the genesis marker's key). * * Artemis and Zuse come from one disk, this system's stick * (docs/v4.0.0/STICK.md 4; plan 2026-10-27-stick-4-one-disk.md, Task 4): its * Artemis partition is attached first -- the first disk slot, where Zuse's * genesis marker is read -- then its identity partition is checked as an * identity drive always has been. No other drive is used on v4; drives of * users minted by this system's Zuse come with the users design after INSTALL. * * Genesis (plan 2026-10-29-stick-5, Task 1; STICK.md 6): a stick whose * Artemis fence has no marker and whose identity partition is blank mints its * own Zuse at its first boot -- the stick's proxy Zuse, full Zuse while no real * Zuse exists -- reads her back, then writes the version 2 marker * (sk_v4_marker.c) naming her. * * Not here (the plan's "not in this gate"): WIREBIND identities, EJECT, * ACL-ZUSE-BOOT. */ #include "starkernel/v4/sk_v4_identity.h" #include "starkernel/console.h" #include "starkernel/arch.h" #include "starkernel/apic.h" #include "starkernel/timer.h" #include "starkernel/kmalloc.h" #include "starkernel/xhci_driver.h" #include "starkernel/blkio_usb.h" #include "starkernel/homeblocks_sig.h" #include "starkernel/artemis_sig.h" #include "starkernel/capsule_zuse_boot.h" #include "starkernel/kbd_input.h" #include "starkernel/gpt.h" #include "starkernel/v4/sk_v4_marker.h" #ifdef ARCH_AMD64 #include "starkernel/ioapic.h" #include "starkernel/i8042.h" #endif #include "block_subsystem.h" #include #define SK_V4_IDLE_BEAT 100u /* ticks between idle beats, v3's SK_IDLE_BEAT_INTERVAL (1 s at 100 Hz) */ /* ZUSE'S STATE, for Hera. The cert is installed once and never replaced * (v3's vm_zuse_cert_install(), one-way); the session ends when her drive * leaves and begins again when it comes back. */ static int g_zuse_session; static int g_zuse_cert_installed; static uint8_t g_zuse_seed[32], g_zuse_pubkey[32]; static uint8_t g_zuse_root_pubkey[32]; static int g_zuse_root_known; static struct blkio_dev *g_zuse_dev; /* The genesis marker on the stick's Artemis fence (version 2: the proxy and * the real Zuse), read when the stick's Artemis partition attaches. */ static sk_v4_marker_t g_marker; /* The proxy is recognised but a real Zuse exists: no session for the proxy * (STICK.md 6a; the fixed screen comes with the plan's Task 5). */ static int g_proxy_fallback; /* A drive in a USB slot: the raw disk; when it is this system's stick, its * StarshipOS identity and Artemis partitions as devices of their own; and * which of them were given to the block subsystem (a disk is never given * whole: docs/v4.0.0/plans/2026-10-26-stick-2-gpt-reader.md, ruled). */ typedef struct { blkio_dev_t raw; blkio_dev_t part; gpt_part_dev_t part_state; blkio_dev_t *used; /* the identity partition, once attached */ blkio_dev_t art; gpt_part_dev_t art_state; blkio_dev_t *art_used; /* the Artemis partition, once attached */ } sk_v4_drive_t; /* One per slot, per xHCI controller, as repl.c's g_usb_ctrl. */ static struct { sk_v4_drive_t *slots; uint32_t count; } g_usb[XHCI_MAX_CONTROLLERS]; #define SK_V4_GPT_PARTS 16u static gpt_part_t g_gpt_parts[SK_V4_GPT_PARTS]; static uint64_t g_last_beat; static const BootDisk *g_boot_disk; /* the loader's: the disk it booted from */ static int g_stick_in; /* a stick's Artemis partition is attached */ static const char *sk_v4_user_prefix(void) { return g_zuse_session ? "zuse" : (const char *)0; } int sk_v4_identity_present(void) { return g_zuse_session; } void sk_v4_identity_boot(const BootDisk *boot_disk) { unsigned xi; static xhci_dev_t xhci_devs[XHCI_MAX_CONTROLLERS]; /* Zuse's public key comes later: from the genesis marker on the stick's * Artemis partition, once it is attached (sk_v4_drive_arrived) */ g_boot_disk = boot_disk; /* every xHCI controller, as kernel_main.c's v3 path: a drive already in * a port is found by the bring-up's own port scan */ for (xi = 0; xi < XHCI_MAX_CONTROLLERS; xi++) { int rc = xhci_find_and_map(&xhci_devs[xi], xi); if (rc == 1) break; if (rc == 0) (void)xhci_bringup(&xhci_devs[xi]); } /* the heartbeat, as kernel_main.c's v3 path starts it */ console_println("Starting heartbeat..."); apic_timer_start(); #ifdef ARCH_AMD64 i8042_drain_stale(); ioapic_unmask_legacy_irq(1); console_println("I/O APIC: keyboard IRQ1 unmasked"); #endif arch_enable_interrupts(); console_println("Heartbeat running."); g_last_beat = heartbeat_ticks(); } /* Is the disk whose GPT was just read (gpt_read gave GPT_OK) this system's * stick? It has both StarshipOS partitions, and it is the disk the loader * booted from -- the same DiskGUID -- or, when the boot was not from a stick * (an ISO, an ESP image), the first such disk while none is attached. */ static int sk_v4_is_this_stick(unsigned nparts) { uint8_t guid[16]; if (gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity) < 0 || gpt_find(g_gpt_parts, nparts, gpt_type_starship_artemis) < 0) return 0; if (g_boot_disk && g_boot_disk->is_stick) { gpt_disk_guid(guid); return memcmp(guid, g_boot_disk->disk_guid, 16) == 0; } return !g_stick_in; } /* Zuse's session begins on @p dev: the console is hers, and the screen with * it (docs/v4.0.0/STICK.md 2). @p line says how she was found. */ static void sk_v4_login(blkio_dev_t *dev, const uint8_t seed[32], const uint8_t pubkey[32], const char *line) { console_set_vm_name("Hera"); console_set_user_prefix_provider(sk_v4_user_prefix); console_fb_mirror(1); console_fb_enable_ttf(); console_println(line); if (!g_zuse_cert_installed) { memcpy(g_zuse_seed, seed, 32); memcpy(g_zuse_pubkey, pubkey, 32); g_zuse_cert_installed = 1; } memcpy(g_zuse_root_pubkey, pubkey, 32); /* as v3's install_and_activate() */ g_zuse_root_known = 1; g_zuse_dev = dev; g_zuse_session = 1; } /* Genesis on the stick (STICK.md 6): the proxy Zuse minted onto the blank * identity partition @p dev, read back, and only then named in the version 2 * marker on @p art's fence. A failure anywhere leaves no marker, and the * system headless. Returns 1 when she is logged in. */ static int sk_v4_genesis(blkio_dev_t *dev, blkio_dev_t *art) { uint8_t seed[32], pubkey[32], rseed[32], rpub[32]; homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; uint32_t lbn, nblk; /* Artemis takes the marker: its volume is formatted at build (STICK.md 4); * confirmed here only because its signature checked out on attach, and a * no-op on a volume already formatted */ if (blk_get_device_range(art, &lbn, &nblk) != BLK_OK || blk_subsys_confirm_format(lbn) != BLK_OK) { console_println("Zuse: genesis mint failed"); return 0; } if (capsule_zuse_boot_genesis_mint(dev, seed, pubkey) != 0) { console_println("Zuse: genesis mint failed"); return 0; } /* read her back: what the marker will name must be on the stick */ sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig); if (capsule_zuse_boot_check_key(dev, sig_rc, &sig, pubkey, rseed, rpub) != ZUSE_DRIVE_HERS || memcmp(rseed, seed, 32) != 0) { console_println("Zuse: genesis mint failed -- not read back"); return 0; } memset(&g_marker, 0, sizeof(g_marker)); g_marker.present = 1; memcpy(g_marker.proxy, pubkey, 32); if (sk_v4_marker_write(&g_marker) != 0) { memset(&g_marker, 0, sizeof(g_marker)); console_println("Zuse: genesis marker write failed -- headless"); return 0; } sk_v4_login(dev, seed, pubkey, "Zuse: genesis minted onto attached thumbdrive"); return 1; } /* A drive that has arrived. This system's stick: its Artemis partition is * attached (the first disk slot, the block subsystem's), Zuse's public key is * read from its genesis marker, then its identity partition is attached and, * if it is Zuse's, her session begins. Any other drive is not used. */ static int sk_v4_drive_arrived(sk_v4_drive_t *d) { blkio_dev_t *dev; homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; artemis_sig_t asig; uint8_t seed[32], pubkey[32]; zuse_drive_t which; unsigned nparts; int gpt_rc; d->used = (blkio_dev_t *)0; d->art_used = (blkio_dev_t *)0; gpt_rc = gpt_read(&d->raw, g_gpt_parts, SK_V4_GPT_PARTS, &nparts); if (gpt_rc == GPT_BAD) { console_println("GPT: a USB drive has a damaged partition table -- not used"); return 1; } if (gpt_rc == GPT_EIO) { console_println("GPT: a USB drive could not be read -- not used"); return 1; } if (gpt_rc != GPT_OK || !sk_v4_is_this_stick(nparts)) { console_println("USB: a drive that is not this system's stick -- not used"); return 1; } if (gpt_part_open(&d->art, &d->art_state, &d->raw, &g_gpt_parts[gpt_find(g_gpt_parts, nparts, gpt_type_starship_artemis)]) != BLKIO_OK || gpt_part_open(&d->part, &d->part_state, &d->raw, &g_gpt_parts[gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity)]) != BLKIO_OK) { console_println("GPT: the stick's partitions are not on 1 KiB blocks -- not used"); return 1; } /* Artemis first: formatted and signed at build (STICK.md 4); a * signature that does not check out is not used, nor is the stick */ if (artemis_sig_check(&d->art, &asig) != ARTEMIS_SIG_OK) { console_println("Artemis: the stick's signature is bad -- not used"); return 1; } if (blk_subsys_attach_device(&d->art) != BLK_OK) { console_println("Artemis: the stick's partition could not be attached"); return 0; } d->art_used = &d->art; g_stick_in = 1; console_println("Artemis: the stick's partition attached"); /* the genesis marker on Artemis's fence: the proxy's key (and the real * Zuse's, once she is minted) */ (void)sk_v4_marker_read(&g_marker); g_zuse_root_known = g_marker.present; if (g_marker.present) memcpy(g_zuse_root_pubkey, g_marker.proxy, 32); /* then the identity partition, as an identity drive always has been */ dev = &d->part; sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig); if (blk_subsys_attach_device(dev) != BLK_OK) { console_println("xhci: USB MSC block-subsystem attach failed"); return 1; } d->used = dev; if (g_zuse_session) return 1; /* no marker: a blank identity partition is the stick's first boot */ if (!g_marker.present) { if (sig_rc == HOMEBLOCKS_SIG_BLANK) (void)sk_v4_genesis(dev, &d->art); return 1; } which = capsule_zuse_boot_check_key(dev, sig_rc, &sig, g_marker.proxy, seed, pubkey); if (which != ZUSE_DRIVE_HERS) return 1; if (g_marker.has_real) { g_proxy_fallback = 1; /* a real Zuse exists: the proxy gets no session */ return 1; } sk_v4_login(dev, seed, pubkey, "Zuse: identity confirmed from attached thumbdrive"); return 1; } /* A drive that has left: its identity partition is let go, then its Artemis * partition -- in that order, because blk_subsys_detach_device() removes * only the chain's tail, and the identity partition was attached last. */ static void sk_v4_drive_left(sk_v4_drive_t *d) { blkio_dev_t *dev = d->used; if (dev) { d->used = (blkio_dev_t *)0; blk_subsys_detach_device(dev); } if (d->art_used) { blk_subsys_detach_device(d->art_used); d->art_used = (blkio_dev_t *)0; g_stick_in = 0; g_proxy_fallback = 0; memset(&g_marker, 0, sizeof(g_marker)); } if (!dev || dev != g_zuse_dev) return; g_zuse_session = 0; g_zuse_dev = (struct blkio_dev *)0; /* nothing of her session is left on the screen; it is dark again -- * except in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs * no login and goes on running (the stick's increment 1 final review) */ #if !EMERGENCY_CONSOLE_ENABLED console_fb_clear(); console_fb_mirror(0); #endif console_println("Zuse: session ended -- reattach to re-authenticate"); } /* One idle beat: the xHCI event ring, then every drive that has arrived or * left since the last (repl.c's sk_repl_idle(), its USB part). */ static void sk_v4_idle(void) { uint32_t xc, slot; console_ensure_line_start(); { uint64_t tx_before = console_tx_count(); xhci_poll_events(); for (xc = 0; xc < xhci_dev_count() && xc < XHCI_MAX_CONTROLLERS; xc++) { xhci_dev_t *xdev = xhci_dev_at(xc); if (!xdev) continue; if (!g_usb[xc].slots || g_usb[xc].count < xdev->max_slots + 1) { size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_v4_drive_t); sk_v4_drive_t *fresh = (sk_v4_drive_t *)kmalloc_aligned(bytes, 64); if (!fresh) continue; memset(fresh, 0, bytes); g_usb[xc].slots = fresh; g_usb[xc].count = xdev->max_slots + 1; } for (slot = 1; slot <= xdev->max_slots; slot++) { xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot); if (!ms || !ms->bot_msc_attach_pending) continue; ms->bot_msc_attach_pending = 0; if (blkio_usb_open_msc(&g_usb[xc].slots[slot].raw, xdev, slot) == 0 && sk_v4_drive_arrived(&g_usb[xc].slots[slot])) ms->bot_msc_attached = 1; } for (slot = 1; slot <= xdev->max_slots; slot++) { xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot); if (!ms || !ms->bot_msc_detach_pending) continue; ms->bot_msc_detach_pending = 0; sk_v4_drive_left(&g_usb[xc].slots[slot]); } } if (console_tx_count() == tx_before) console_cancel_deferred_line_start(); } } void sk_v4_identity_beat(void) { uint64_t now; heartbeat_service(); now = heartbeat_ticks(); if (now - g_last_beat >= SK_V4_IDLE_BEAT) { g_last_beat = now; sk_v4_idle(); } } void sk_v4_identity_check_now(void) { g_last_beat = heartbeat_ticks(); sk_v4_idle(); } /* What was typed at a headless machine -- on the serial line or any * keyboard -- is no one's: it is read and dropped, so that it does not run * as whoever logs in next (the identity gate's final review, item 4, * 2026-10-26). */ static void sk_v4_discard_input(void) { sk_input_drop(); } void sk_v4_identity_wait(void) { while (!g_zuse_session) { sk_v4_discard_input(); sk_v4_identity_beat(); arch_halt(); /* headless, until Zuse: wait for the next tick, not a spin (#151) */ } sk_v4_discard_input(); }