Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions capiscio_sdk/simple_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ def _load_or_generate_keys(self) -> None:
else:
raise ConfigurationError(f"capiscio_keys directory not found at {self.keys_dir}")

did_key_path = self.keys_dir / "did_key.txt"

if private_key_path.exists():
# Load existing key via gRPC
key_info, error = self._client.simpleguard.load_key(str(private_key_path))
Expand All @@ -308,6 +310,11 @@ def _load_or_generate_keys(self) -> None:
# Update signing kid to match the loaded key
self.signing_kid = key_info["key_id"]
logger.info(f"Loaded key: {self.signing_kid}")

# Recover did:key identity from sidecar file if in dev mode
if self.dev_mode and not self._explicit_agent_id and did_key_path.exists():
self.agent_id = did_key_path.read_text().strip()
logger.info(f"Dev Mode: Recovered did:key identity: {self.agent_id}")
Comment on lines +316 to +317
Comment on lines +314 to +317
elif self.dev_mode:
logger.info("Dev Mode: Generating Ed25519 keypair via gRPC")

Expand All @@ -323,6 +330,8 @@ def _load_or_generate_keys(self) -> None:
if did_key and not self._explicit_agent_id:
self.agent_id = did_key
logger.info(f"Dev Mode: Using did:key identity: {self.agent_id}")
# Persist did:key for recovery on subsequent loads
did_key_path.write_text(did_key)
Comment on lines +333 to +334

# Save private key
with open(private_key_path, "w") as f:
Expand Down
52 changes: 52 additions & 0 deletions tests/unit/test_simple_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,58 @@ def test_legacy_agent_card_with_deprecation_warning(self, temp_workspace, mock_r
guard.close()


class TestSimpleGuardDidKeyRecovery:
"""Tests for dev mode did:key persistence and recovery."""

def test_dev_mode_persists_did_key_on_generate(self, temp_workspace, mock_rpc_client):
"""Test that dev_mode persists did:key to sidecar file on first generation."""
mock_rpc_client.simpleguard.generate_key_pair.return_value = ({
"key_id": "test-key",
"did_key": "did:key:z6MkTestGeneratedKey",
"private_key_pem": "-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----",
}, None)

guard = SimpleGuard(dev_mode=True)

assert guard.agent_id == "did:key:z6MkTestGeneratedKey"
did_key_path = temp_workspace / "capiscio_keys" / "did_key.txt"
assert did_key_path.exists()
assert did_key_path.read_text() == "did:key:z6MkTestGeneratedKey"

guard.close()

def test_dev_mode_recovers_did_key_on_load(self, temp_workspace, mock_rpc_client):
"""Test that dev_mode recovers did:key from sidecar file when key exists."""
keys_dir = temp_workspace / "capiscio_keys"
keys_dir.mkdir(parents=True)
(keys_dir / "trusted").mkdir()
(keys_dir / "private.pem").write_text("mock key")
(keys_dir / "public.pem").write_text("mock pub")
(keys_dir / "did_key.txt").write_text("did:key:z6MkRecoveredKey")

guard = SimpleGuard(dev_mode=True)

assert guard.agent_id == "did:key:z6MkRecoveredKey"
mock_rpc_client.simpleguard.generate_key_pair.assert_not_called()

guard.close()

def test_dev_mode_explicit_agent_id_ignores_sidecar(self, temp_workspace, mock_rpc_client):
"""Test that explicit agent_id is not overridden by sidecar did_key.txt."""
keys_dir = temp_workspace / "capiscio_keys"
keys_dir.mkdir(parents=True)
(keys_dir / "trusted").mkdir()
(keys_dir / "private.pem").write_text("mock key")
(keys_dir / "did_key.txt").write_text("did:key:z6MkShouldBeIgnored")

guard = SimpleGuard(dev_mode=True, agent_id="did:web:example.com:myagent")

assert guard.agent_id == "did:web:example.com:myagent"

guard.close()


class TestSimpleGuardSigning:
"""Tests for SimpleGuard signing operations."""

Expand Down
Loading