Milestone 6: mkcapsule signing + capsule_birth.c wiring, WARN-only
First attempt shelled out to `openssl pkeyutl -sign` (fork/execlp, not system() -- avoided shell string interpolation of the key path). Corrected on request: no new external host binary dependency when the repo's own code can do the job -- same standing preference as the earlier anti-file correction. Rewritten to link ed25519_sign() (already verified against OpenSSL in Phase B) directly into mkcapsule. New tools/pkcs8_ed25519.c: a narrow DER walker (same shape as x509_ed25519.c, deliberately not shared -- small enough that duplicating a few TLV-walking lines beat threading a header between the kernel crypto tree and host tooling) extracting the raw seed from the intermediate's PKCS#8 private key, plus a minimal self-written base64 decoder (PEM is openssl genpkey's default output; no decoder existed anywhere in the repo). Verified end-to-end before wiring anything in: the extracted seed's derived pubkey matches the cert's exactly, and a full self-contained sign+verify round-trip (zero openssl) passes. CapsuleDesc had no spare bytes, so signatures live in a new parallel CapsuleSigEntry array, emitted by a new `mkcapsule --sign-key <path>` flag (omitted/missing key -> has_sig=0 everywhere, graceful, not a build failure -- CI has no access to the offline key). New capsule_sig.c/.h: capsule_verify_signature(), a separate function, not folded into the already-tested capsule_validate(). Finds and caches the embedded intermediate cert's pubkey once per boot, then verifies against it. Wired into all three capsule_validate() call sites in capsule_birth.c via log_message(LOG_WARN, ...) -- never refuses yet, per the earlier staged-rollout decision. Verified independently, both directions, live in the real kernel: a full clean build (38 signed capsules) boots clean on all three architectures with zero warnings. Separately, hand-corrupted one byte of Mama's own init.4th capsule's stored signature (not its payload/hash, which capsule_validate() already catches and would have masked the test) and rebuilt just the changed object: produced exactly "capsule sig: init.4th: INVALID -- signature does not verify" on boot, and the kernel still reached ok> -- proving warn-only doesn't refuse anything yet. Reverted before the final, untampered 3-arch acceptance pass. Still open: flipping WARN to hard-refuse (separate, deliberate step) and the BLOCK_MAP.md signature-status column. Documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
431bcb1f34
commit
2fc55f47e1
@@ -29,11 +29,18 @@
|
||||
#include "starkernel/capsule_birth.h"
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_run.h"
|
||||
#include "starkernel/capsule_generated.h" /* capsule_get_signatures() */
|
||||
#include "starkernel/capsule_sig.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
|
||||
#include "vm.h"
|
||||
#include "platform_alloc.h"
|
||||
/* log.h after vm.h: vm.h's own LOG_LINE_MAX (persistent block-log,
|
||||
* unrelated concept, unconditional #define) must win before log.h's
|
||||
* #ifndef-guarded one sees it -- reversed order redefines and fails
|
||||
* -Werror (found 2026-08-26 wiring in capsule signature logging). */
|
||||
#include "log.h"
|
||||
|
||||
/*===========================================================================
|
||||
* VM Execution Hooks
|
||||
@@ -409,6 +416,19 @@ CapsuleRunResult capsule_birth_mama(
|
||||
CapsuleValidateResult vr = capsule_validate(mama_cap, arena, dir->arena_size, 1);
|
||||
if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID;
|
||||
|
||||
/* Milestone 6 (Phase 8): WARN-only signature check, not yet enforced --
|
||||
* see capsule_sig.h's own doc comment for why (blast radius: a false
|
||||
* refusal here means Mama herself never births, no ok> on any arch). */
|
||||
{
|
||||
int idx = (int)(mama_cap - descs);
|
||||
CapsuleSigResult sr = capsule_verify_signature(
|
||||
descs, names, capsule_get_signatures(), arena, dir->desc_count, idx);
|
||||
if (sr != CAPSULE_SIG_OK) {
|
||||
log_message(LOG_WARN, "capsule sig: %s: %s",
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t pre_dict_hash = vm_dict_hash_fn(mama_vm);
|
||||
(void)pre_dict_hash;
|
||||
|
||||
@@ -468,6 +488,17 @@ CapsuleRunResult capsule_birth_baby(
|
||||
CapsuleValidateResult vr = capsule_validate(cap, arena, dir->arena_size, 1);
|
||||
if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID;
|
||||
|
||||
/* Milestone 6 (Phase 8): WARN-only, see capsule_sig.h. */
|
||||
{
|
||||
int idx = (int)(cap - descs);
|
||||
CapsuleSigResult sr = capsule_verify_signature(
|
||||
descs, names, capsule_get_signatures(), arena, dir->desc_count, idx);
|
||||
if (sr != CAPSULE_SIG_OK) {
|
||||
log_message(LOG_WARN, "capsule sig: %s: %s",
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
}
|
||||
}
|
||||
|
||||
if (vm_registry_live_count() >= stadium_max_vm_count()) {
|
||||
capsule_parity_log_birth_failed(vm_uuid_none(), cap->capsule_id,
|
||||
CAPSULE_RUN_ERR_FLEET_FULL, 0);
|
||||
@@ -603,6 +634,17 @@ CapsuleRunResult capsule_run_experiment(
|
||||
CapsuleValidateResult vr = capsule_validate(cap, arena, dir->arena_size, 1);
|
||||
if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID;
|
||||
|
||||
/* Milestone 6 (Phase 8): WARN-only, see capsule_sig.h. */
|
||||
{
|
||||
int idx = (int)(cap - descs);
|
||||
CapsuleSigResult sr = capsule_verify_signature(
|
||||
descs, names, capsule_get_signatures(), arena, dir->desc_count, idx);
|
||||
if (sr != CAPSULE_SIG_OK) {
|
||||
log_message(LOG_WARN, "capsule sig: %s: %s",
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t pre_dict_hash = vm_dict_hash_fn(mama_vm);
|
||||
|
||||
const uint8_t *payload = capsule_get_payload(cap, arena);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* capsule_sig.c -- see capsule_sig.h. */
|
||||
#include "starkernel/capsule_sig.h"
|
||||
#include "starkernel/x509_ed25519.h"
|
||||
#include "starkernel/ed25519.h"
|
||||
#include <string.h>
|
||||
|
||||
#define SNAKEOIL_CERT_NAME "pki:snakeoil-intermediate.der"
|
||||
|
||||
/* Cached once per boot: the cert doesn't change mid-boot, and every
|
||||
* capsule birth (Mama, then every child VM) would otherwise re-find and
|
||||
* re-parse the same cert. -1 = not yet attempted, 0 = failed, 1 = ready. */
|
||||
static int pubkey_state = -1;
|
||||
static uint8_t cached_pubkey[32];
|
||||
|
||||
static int find_and_cache_pubkey(const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena_base,
|
||||
uint32_t desc_count) {
|
||||
if (pubkey_state != -1) return pubkey_state;
|
||||
|
||||
pubkey_state = 0; /* assume failure unless every step below succeeds */
|
||||
for (uint32_t i = 0; i < desc_count; i++) {
|
||||
if (strcmp(names[i].name, SNAKEOIL_CERT_NAME) == 0) {
|
||||
const CapsuleDesc *cert_desc = &descs[i];
|
||||
if (x509_extract_ed25519_pubkey(arena_base + cert_desc->offset,
|
||||
(size_t)cert_desc->length,
|
||||
cached_pubkey) == 0) {
|
||||
pubkey_state = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pubkey_state;
|
||||
}
|
||||
|
||||
CapsuleSigResult capsule_verify_signature(
|
||||
const CapsuleDesc *descs, const CapsuleNameEntry *names,
|
||||
const CapsuleSigEntry *sigs, const uint8_t *arena_base,
|
||||
uint32_t desc_count, int index) {
|
||||
|
||||
if (!descs || !names || !sigs || !arena_base || index < 0 ||
|
||||
(uint32_t)index >= desc_count) {
|
||||
return CAPSULE_SIG_NO_ROOT_KEY; /* malformed call -- treat like "can't check" */
|
||||
}
|
||||
|
||||
if (!find_and_cache_pubkey(descs, names, arena_base, desc_count)) {
|
||||
return CAPSULE_SIG_NO_ROOT_KEY;
|
||||
}
|
||||
|
||||
if (!sigs[index].has_sig) {
|
||||
return CAPSULE_SIG_MISSING;
|
||||
}
|
||||
|
||||
const CapsuleDesc *d = &descs[index];
|
||||
int ok = ed25519_verify(cached_pubkey, arena_base + d->offset,
|
||||
(size_t)d->length, sigs[index].sig);
|
||||
return ok ? CAPSULE_SIG_OK : CAPSULE_SIG_INVALID;
|
||||
}
|
||||
|
||||
const char *capsule_sig_result_str(CapsuleSigResult result) {
|
||||
switch (result) {
|
||||
case CAPSULE_SIG_OK: return "ok";
|
||||
case CAPSULE_SIG_MISSING: return "missing (unsigned)";
|
||||
case CAPSULE_SIG_INVALID: return "INVALID -- signature does not verify";
|
||||
case CAPSULE_SIG_NO_ROOT_KEY: return "no root key (cert not found/parseable)";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user