Files
LithosAnanake/include/starkernel/capsule.h
T
Robert Allan JamesandClaude Sonnet 5 2fc55f47e1 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
2026-08-26 21:32:29 -04:00

295 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* capsule.h - Init Capsule Architecture (M7.1)
*
* Content-addressed, immutable init capsules for VM birth.
* See docs/lithosananke/M7.1.md for full specification.
*
* Key invariants:
* - Exactly ONE production (p) INIT defines a baby VM
* - capsule_id == content_hash (content-addressed)
* - No shared/implicit base INITs
* - DOMAIN is Mama-only, PERSONALITY is baby-only
*/
#ifndef STARKERNEL_CAPSULE_H
#define STARKERNEL_CAPSULE_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*===========================================================================
* Constants
*===========================================================================*/
/** Magic signatures */
#define CAPSULE_DESC_MAGIC 0x53504143ULL /* 'CAPS' little-endian */
#define CAPSULE_DIR_MAGIC 0x44504143ULL /* 'CAPD' little-endian */
/** Version */
#define CAPSULE_VERSION_0 0
/** Limits */
#define CAPSULE_MAX_COUNT 256
/** Maximum capsule name length (colon-separated path, null-terminated) */
#define CAPSULE_NAME_MAX 512
/*===========================================================================
* Hash Algorithm Enum
*===========================================================================*/
typedef enum {
CAPSULE_HASH_XXHASH64 = 0,
CAPSULE_HASH_SHA256 = 1,
CAPSULE_HASH_BLAKE3 = 2,
} CapsuleHashAlg;
/*===========================================================================
* Flags
*===========================================================================*/
/** State flags */
#define CAPSULE_FLAG_ACTIVE 0x00000001 /* Eligible for use */
#define CAPSULE_FLAG_REVOKED 0x00000002 /* Birth-blocked forever */
#define CAPSULE_FLAG_DEPRECATED 0x00000004 /* Eligible but discouraged */
#define CAPSULE_FLAG_PINNED 0x00000008 /* Immune to GC */
/** Mode flags (D2: babies carry both) */
#define CAPSULE_FLAG_PRODUCTION 0x00000010 /* (p) truth-bearing */
#define CAPSULE_FLAG_EXPERIMENT 0x00000020 /* (e) workload only */
/** Mama init flag (exactly one capsule must have this) */
#define CAPSULE_FLAG_MAMA_INIT 0x00000040 /* (m) Mama's init */
/** Validate mode flags.
* Mama: neither (p) nor (e) may be set.
* Babies: at least one of (p) or (e) must be set (both is fine — D2). */
#define CAPSULE_MODE_VALID(f) \
((((f) & CAPSULE_FLAG_MAMA_INIT) != 0) ? \
(!((f) & (CAPSULE_FLAG_PRODUCTION | CAPSULE_FLAG_EXPERIMENT))) : \
(((f) & CAPSULE_FLAG_PRODUCTION) || ((f) & CAPSULE_FLAG_EXPERIMENT)))
/** Check if capsule is Mama's init */
#define CAPSULE_IS_MAMA_INIT(f) \
(((f) & CAPSULE_FLAG_MAMA_INIT) && ((f) & CAPSULE_FLAG_ACTIVE))
/** Birth eligibility: active and not revoked (flag type irrelevant — D2) */
#define CAPSULE_BIRTH_ELIGIBLE(f) \
(((f) & CAPSULE_FLAG_ACTIVE) && \
!((f) & CAPSULE_FLAG_REVOKED))
/** DoE eligibility: experiment, active, not revoked */
#define CAPSULE_DOE_ELIGIBLE(f) \
(((f) & CAPSULE_FLAG_EXPERIMENT) && \
((f) & CAPSULE_FLAG_ACTIVE) && \
!((f) & CAPSULE_FLAG_REVOKED))
/*===========================================================================
* Magic Field Packing
*
* bits 0..31 : 'CAPS' (0x53504143 little-endian)
* bits 32..39 : version (0 for v0)
* bits 40..47 : hashAlg (enum CapsuleHashAlg)
* bits 48..63 : reserved (zero)
*===========================================================================*/
#define CAPSULE_MAGIC_PACK(ver, alg) \
(CAPSULE_DESC_MAGIC | ((uint64_t)(ver) << 32) | ((uint64_t)(alg) << 40))
#define CAPSULE_MAGIC_GET_SIG(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
#define CAPSULE_MAGIC_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
#define CAPSULE_MAGIC_GET_HASHALG(m) ((uint8_t)(((m) >> 40) & 0xFF))
/*===========================================================================
* CapsuleDesc - Capsule Descriptor (64 bytes, cache-line aligned)
*===========================================================================*/
typedef struct __attribute__((aligned(64))) {
uint64_t magic; /* 0x00: 'CAPS' | ver | hashAlg | reserved */
uint64_t capsule_id; /* 0x08: == content_hash (content-addressed) */
uint64_t content_hash; /* 0x10: hash of payload bytes */
uint64_t offset; /* 0x18: byte offset into payload arena */
uint64_t length; /* 0x20: payload length in bytes */
uint32_t flags; /* 0x28: state + policy bits */
uint32_t owner_vm; /* 0x2C: 0 = mama, else child VM ID */
uint64_t birth_count; /* 0x30: how many VMs born from this */
uint64_t created_ns; /* 0x38: monotonic timestamp at registration */
} CapsuleDesc; /* 0x40 = 64 bytes */
/*===========================================================================
* CapsuleNameEntry - Capsule Name (parallel array to CapsuleDesc[])
*
* Indexed 1:1 with capsule_descriptors[]. Name is the full relative path
* from the capsule root with '/' replaced by ':', e.g.:
* "core:init.4th"
* "experiments:doe-l8:init-l8-diverse.4th"
* "production:myvm.4th"
*===========================================================================*/
typedef struct {
char name[CAPSULE_NAME_MAX]; /* null-terminated, colon-separated path */
} CapsuleNameEntry;
/*===========================================================================
* CapsuleSigEntry - Ed25519 signature (parallel array to CapsuleDesc[])
*
* Milestone 6 (Phase 8): each capsule's payload bytes (the same bytes
* content_hash already covers), signed by mkcapsule at build time with
* the snakeoil intermediate's private key. has_sig=0 for a capsule built
* before this feature existed or otherwise unsigned -- a real, distinct
* state, not "signature is all-zero bytes" (which sig[64] full of 0x00
* would otherwise look ambiguous with). Indexed 1:1 with
* capsule_descriptors[], same convention as CapsuleNameEntry.
*===========================================================================*/
typedef struct {
uint8_t sig[64]; /* raw Ed25519 R||S, see ed25519_sign()/ed25519_verify() */
uint8_t has_sig; /* 0 = no signature present, 1 = sig[] is real */
uint8_t _pad[7];
} CapsuleSigEntry;
/*===========================================================================
* CapsuleDirHeader - Directory Header
*===========================================================================*/
typedef struct {
uint64_t magic; /* 'CAPD' | ver | reserved */
uint64_t arena_base; /* phys or virt base of payload arena */
uint64_t arena_size; /* bytes */
uint32_t desc_count; /* current number of descriptors */
uint32_t desc_capacity; /* max (fixed at compile time for Phase A) */
uint32_t name_count; /* == desc_count, kept separate for validation */
uint32_t reserved; /* padding */
uint64_t dir_hash; /* hash of descriptor table (for parity) */
} CapsuleDirHeader;
/*===========================================================================
* Validation
*===========================================================================*/
typedef enum {
CAPSULE_VALID = 0,
CAPSULE_ERR_BAD_MAGIC,
CAPSULE_ERR_BAD_VERSION,
CAPSULE_ERR_BAD_HASH_ALG,
CAPSULE_ERR_BOUNDS,
CAPSULE_ERR_MODE_INVALID,
CAPSULE_ERR_REVOKED_ACTIVE,
CAPSULE_ERR_HASH_MISMATCH,
CAPSULE_ERR_NULL_PTR,
} CapsuleValidateResult;
/**
* capsule_validate - Validate a capsule descriptor
*
* @param desc Capsule descriptor to validate
* @param arena_base Base address of payload arena
* @param arena_size Size of payload arena in bytes
* @param verify_hash If true, recompute and compare content hash
* @return CAPSULE_VALID on success, error code otherwise
*/
CapsuleValidateResult capsule_validate(
const CapsuleDesc *desc,
const uint8_t *arena_base,
uint64_t arena_size,
int verify_hash
);
/**
* capsule_validate_result_str - Get string for validation result
*/
const char *capsule_validate_result_str(CapsuleValidateResult result);
/*===========================================================================
* Lookup
*===========================================================================*/
/**
* capsule_find_by_id - Find capsule by content hash ID
*
* @param dir Directory header
* @param descs Descriptor array
* @param id Capsule ID (content hash) to find
* @return Pointer to descriptor, or NULL if not found
*/
const CapsuleDesc *capsule_find_by_id(
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
uint64_t id
);
/**
* capsule_find_by_name - Find capsule by colon-separated name
*
* @param dir Directory header
* @param descs Descriptor array
* @param names Name entry array (parallel to descs)
* @param name Colon-separated capsule name, e.g. "core:init.4th"
* @return Pointer to descriptor, or NULL if not found
*/
const CapsuleDesc *capsule_find_by_name(
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const char *name
);
/**
* capsule_get_payload - Get pointer to capsule payload bytes
*
* @param desc Capsule descriptor
* @param arena_base Base address of payload arena
* @return Pointer to payload bytes, or NULL on error
*/
const uint8_t *capsule_get_payload(
const CapsuleDesc *desc,
const uint8_t *arena_base
);
/**
* capsule_find_mama_init - Find the Mama init capsule
*
* Searches the descriptor array for the capsule with CAPSULE_FLAG_MAMA_INIT.
* There must be exactly one such capsule.
*
* @param dir Directory header
* @param descs Descriptor array
* @return Pointer to Mama's init descriptor, or NULL if not found
*/
const CapsuleDesc *capsule_find_mama_init(
const CapsuleDirHeader *dir,
const CapsuleDesc *descs
);
#ifdef __cplusplus
}
#endif
#endif /* STARKERNEL_CAPSULE_H */