gabriel / muse public
test_agent_key_fd_zeroing.py python
94 lines 3.6 KB
Raw
sha256:f6cd81bc71702f5c1c6890bd39aaba994fe58c75f019d7c03934724fa2739bb4 fix: carry dev changes harmony dropped in merge — detached … Sonnet 4.6 minor ⚠ breaking 15 days ago
1 """Tests for DerivedKey zeroing in get_signing_identity (MUSE_AGENT_KEY_FD path).
2
3 get_signing_identity reads a 64-byte sub-seed from MUSE_AGENT_KEY_FD, calls
4 derive_identity_key() to produce a DerivedKey, creates the Ed25519 private
5 key from it, then zeros the sub_seed bytearray. But it did NOT call
6 dk.zero() — leaving the DerivedKey's 32-byte private_bytes and chain_code in
7 heap memory indefinitely.
8
9 Fix:
10 - Call dk.zero() immediately after Ed25519PrivateKey.from_private_bytes().
11 - The Ed25519 key object holds the key material; the DerivedKey is no
12 longer needed and must be wiped.
13
14 Coverage
15 --------
16 I DerivedKey is zeroed after get_signing_identity returns (MUSE_AGENT_KEY_FD)
17 I1 private_bytes is all-zero in the DerivedKey after signing identity resolved
18 I2 chain_code is all-zero in the DerivedKey after signing identity resolved
19 """
20
21 from __future__ import annotations
22
23 import os
24 from unittest.mock import patch
25
26 import pytest
27
28 from muse.core import hdkeys as _hdkeys
29 from muse.core.slip010 import DerivedKey
30 from muse.core.bip39 import mnemonic_to_seed
31 from muse.core.hdkeys import derive_agent_sub_seed, DOMAIN_IDENTITY
32 from muse.cli import config as config_mod
33
34 _MNEMONIC = (
35 "abandon abandon abandon abandon abandon abandon abandon abandon "
36 "abandon abandon abandon about"
37 )
38 _SEED = mnemonic_to_seed(_MNEMONIC)
39 _SUB_SEED = derive_agent_sub_seed(_SEED, domain=DOMAIN_IDENTITY, agent_id=0)
40
41
42 class TestAgentKeyFdDerivedKeyZeroed:
43 def _make_fd(self) -> int:
44 """Create a pipe and write 64 bytes of sub-seed; return the read end fd."""
45 r, w = os.pipe()
46 os.write(w, bytes(_SUB_SEED))
47 os.close(w)
48 return r
49
50 def test_I1_private_bytes_zeroed_after_fd_read(self, monkeypatch: pytest.MonkeyPatch) -> None:
51 """I1: DerivedKey.private_bytes is zeroed after the signing identity is resolved."""
52 captured: list[DerivedKey] = []
53 original_derive = _hdkeys.derive_identity_key
54
55 def capturing_derive(*args: int | bytes, **kwargs: int) -> DerivedKey:
56 dk = original_derive(*args, **kwargs)
57 captured.append(dk)
58 return dk
59
60 r_fd = self._make_fd()
61 monkeypatch.setenv("MUSE_AGENT_KEY_FD", str(r_fd))
62 monkeypatch.setenv("MUSE_AGENT_HANDLE", "test-agent")
63
64 with patch.object(_hdkeys, "derive_identity_key", side_effect=capturing_derive):
65 config_mod.get_signing_identity()
66
67 assert captured, "derive_identity_key was not called via MUSE_AGENT_KEY_FD path"
68 dk = captured[0]
69 assert dk.private_bytes == bytearray(32), (
70 "DerivedKey.private_bytes must be zeroed after get_signing_identity returns"
71 )
72
73 def test_I2_chain_code_zeroed_after_fd_read(self, monkeypatch: pytest.MonkeyPatch) -> None:
74 """I2: DerivedKey.chain_code is zeroed after the signing identity is resolved."""
75 captured: list[DerivedKey] = []
76 original_derive = _hdkeys.derive_identity_key
77
78 def capturing_derive(*args: int | bytes, **kwargs: int) -> DerivedKey:
79 dk = original_derive(*args, **kwargs)
80 captured.append(dk)
81 return dk
82
83 r_fd = self._make_fd()
84 monkeypatch.setenv("MUSE_AGENT_KEY_FD", str(r_fd))
85 monkeypatch.setenv("MUSE_AGENT_HANDLE", "test-agent")
86
87 with patch.object(_hdkeys, "derive_identity_key", side_effect=capturing_derive):
88 config_mod.get_signing_identity()
89
90 assert captured, "derive_identity_key was not called via MUSE_AGENT_KEY_FD path"
91 dk = captured[0]
92 assert dk.chain_code == bytearray(32), (
93 "DerivedKey.chain_code must be zeroed after get_signing_identity returns"
94 )
File History 2 commits
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 20 days ago