From f3eac6b86f402495f5f31b478d41fa1761f979ee Mon Sep 17 00:00:00 2001 From: Gnani Rahul Nutakki Date: Wed, 22 Jul 2026 21:51:31 -0500 Subject: [PATCH] fix(hub): clear parser-owned session key bytes Copy the validated Ed25519 key into runtime-owned memory, then clear the parser-owned allocation before returning. Lock the ownership transfer with a focused non-aliasing and signing regression. GSTACK-Checkpoint: 2026-07-22/e3-session-key-lifetime#1 Signed-off-by: Gnani Rahul Nutakki --- internal/hubruntime/config.go | 8 +++++++- internal/hubruntime/runtime_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/internal/hubruntime/config.go b/internal/hubruntime/config.go index 84c7cf6..91dd5ff 100644 --- a/internal/hubruntime/config.go +++ b/internal/hubruntime/config.go @@ -549,7 +549,13 @@ func loadSessionPrivateKey(path string) (ed25519.PrivateKey, error) { if !ok || len(key) != ed25519.PrivateKeySize { return nil, fmt.Errorf("load hub configuration: session private key is not Ed25519") } - return append(ed25519.PrivateKey(nil), key...), nil + return copyAndClearSessionPrivateKey(key), nil +} + +func copyAndClearSessionPrivateKey(key ed25519.PrivateKey) ed25519.PrivateKey { + copied := append(ed25519.PrivateKey(nil), key...) + clear(key) + return copied } func readMountedFile(label, path string, maxBytes int) ([]byte, error) { diff --git a/internal/hubruntime/runtime_test.go b/internal/hubruntime/runtime_test.go index 51fd645..2fdd9e0 100644 --- a/internal/hubruntime/runtime_test.go +++ b/internal/hubruntime/runtime_test.go @@ -3,6 +3,7 @@ package hubruntime import ( + "bytes" "context" "crypto/ed25519" "crypto/rand" @@ -306,6 +307,33 @@ func TestLoadSessionPrivateKeyRequiresReadOnlyPKCS8Ed25519(t *testing.T) { } } +func TestCopyAndClearSessionPrivateKeyTransfersOwnership(t *testing.T) { + publicKey, parsedKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + expected := append(ed25519.PrivateKey(nil), parsedKey...) + returned := copyAndClearSessionPrivateKey(parsedKey) + t.Cleanup(func() { + clear(expected) + clear(returned) + }) + + if &parsedKey[0] == &returned[0] { + t.Fatal("returned private key aliases the parser-owned key") + } + if !bytes.Equal(parsedKey, make([]byte, ed25519.PrivateKeySize)) { + t.Fatal("parser-owned private key was not cleared") + } + if !bytes.Equal(returned, expected) { + t.Fatal("returned private key changed during ownership transfer") + } + message := []byte("sith session key ownership transfer") + if !ed25519.Verify(publicKey, message, ed25519.Sign(returned, message)) { + t.Fatal("returned private key cannot produce a valid signature") + } +} + func runtimeTestTLS(t *testing.T) (*tls.Config, *tls.Config) { t.Helper() publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)