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
@@ -156,6 +156,24 @@ 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
|
||||
*===========================================================================*/
|
||||
|
||||
@@ -31,6 +31,8 @@ extern const CapsuleDesc capsule_descriptors[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleNameEntry capsule_names[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleSigEntry capsule_signatures[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleDirHeader capsule_directory;
|
||||
|
||||
/*
|
||||
@@ -50,6 +52,8 @@ const CapsuleDesc *capsule_get_descriptors(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleNameEntry *capsule_get_names(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleSigEntry *capsule_get_signatures(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const uint8_t *capsule_get_arena(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* capsule_sig.h -- per-capsule Ed25519 signature verification
|
||||
* (Milestone 6, Phase 8). Deliberately kept separate from
|
||||
* capsule_validate.c: that function is already tested and its
|
||||
* signature/behavior stays untouched; this is a new, additive check
|
||||
* called alongside it, not folded into it.
|
||||
*
|
||||
* Currently WARN-only, not enforced -- see FABRIC-3.md's Milestone 6
|
||||
* rollout decision. A bug here has a larger blast radius than most of
|
||||
* this project's other checks: a false refusal on Mama's own capsule
|
||||
* would mean no `ok>` at all, on any architecture. Land warn-only,
|
||||
* prove correct against both a valid and a deliberately-corrupted
|
||||
* capsule on all three architectures, then flip to hard-refuse
|
||||
* separately.
|
||||
*/
|
||||
#ifndef STARKERNEL_CAPSULE_SIG_H
|
||||
#define STARKERNEL_CAPSULE_SIG_H
|
||||
|
||||
#include "starkernel/capsule.h"
|
||||
|
||||
typedef enum {
|
||||
CAPSULE_SIG_OK = 0, /* has_sig=1, and it verifies */
|
||||
CAPSULE_SIG_MISSING, /* has_sig=0 -- not signed at all */
|
||||
CAPSULE_SIG_INVALID, /* has_sig=1 but verification failed */
|
||||
CAPSULE_SIG_NO_ROOT_KEY, /* couldn't find/parse the embedded intermediate cert */
|
||||
} CapsuleSigResult;
|
||||
|
||||
/*
|
||||
* Verify capsule descs[index]'s Ed25519 signature against the embedded
|
||||
* snakeoil intermediate cert's public key (capsule name
|
||||
* "pki:snakeoil-intermediate.der", found and parsed once, cached for
|
||||
* every later call this boot -- the cert doesn't change mid-boot).
|
||||
*
|
||||
* descs/names/sigs must be the same three parallel arrays
|
||||
* (capsule_get_descriptors()/capsule_get_names()/capsule_get_signatures()),
|
||||
* desc_count their shared length, arena_base the payload arena
|
||||
* (capsule_get_arena()). index must be < desc_count.
|
||||
*/
|
||||
CapsuleSigResult capsule_verify_signature(
|
||||
const CapsuleDesc *descs, const CapsuleNameEntry *names,
|
||||
const CapsuleSigEntry *sigs, const uint8_t *arena_base,
|
||||
uint32_t desc_count, int index);
|
||||
|
||||
/* Human-readable string for logging, mirroring
|
||||
* capsule_validate_result_str()'s existing shape. */
|
||||
const char *capsule_sig_result_str(CapsuleSigResult result);
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_SIG_H */
|
||||
Reference in New Issue
Block a user