sigil/tests/test_keys.py
bigpickle f4a044f1d6 Sigil: agent identity for the new internet
Replace passcode identity with Ed25519 challenge-response.

- Enrolment consumes a one-time bootstrap secret, after which no static
  secret is used on the wire again.
- Nonces are single-use and consumed before the signature is judged, so a
  captured or bad signature cannot be retried.
- Sessions are short-lived, scoped, and typed agent vs human_via_agent.
- Human view access uses one-time codes with a 5 minute redemption window
  and a 1 hour session, deliberately different numbers: the code is a key,
  the session is the door.
- Invariant: only an agent signature may act as an agent. Enforced and tested.
- Append-only audit trail.

54 tests, covering refusals rather than only the happy path.

Reference implementation, not reviewed or hardened. See README Status.
2026-10-06 06:28:01 +08:00

76 lines
2.8 KiB
Python

"""Keys: signing, verification, and the fingerprint as a safe identifier."""
import base64
import os
import tempfile
import unittest
from sigil import keys
class TestKeys(unittest.TestCase):
def test_sign_verify_roundtrip(self):
private, public = keys.generate()
message = b"the exact bytes that were challenged"
signature = keys.sign(private, message)
self.assertTrue(keys.verify(public, signature, message))
def test_wrong_message_fails(self):
private, public = keys.generate()
signature = keys.sign(private, b"nonce-1234")
self.assertFalse(keys.verify(public, signature, b"nonce-5678"))
def test_wrong_key_fails(self):
private_a, _ = keys.generate()
_, public_b = keys.generate()
signature = keys.sign(private_a, b"nonce-1234")
self.assertFalse(keys.verify(public_b, signature, b"nonce-1234"))
def test_tampered_signature_fails(self):
private, public = keys.generate()
signature = bytearray(keys.sign(private, b"nonce-1234"))
signature[0] ^= 0xFF
self.assertFalse(keys.verify(public, bytes(signature), b"nonce-1234"))
def test_garbage_signature_does_not_raise(self):
_, public = keys.generate()
self.assertFalse(keys.verify(public, b"not-a-signature", b"x"))
self.assertFalse(keys.verify(public, b"", b"x"))
def test_public_b64_roundtrip(self):
_, public = keys.generate()
restored = keys.import_public_b64(keys.export_public_b64(public))
self.assertEqual(
keys.public_bytes(restored), keys.public_bytes(public)
)
self.assertEqual(keys.fingerprint(restored), keys.fingerprint(public))
def test_fingerprint_is_stable_and_identifier_shaped(self):
_, public = keys.generate()
first = keys.fingerprint(public)
second = keys.fingerprint(public)
self.assertEqual(first, second)
self.assertTrue(first.startswith("ed25519:"))
groups = first.split(":")[1:]
self.assertEqual(len(groups), 4)
self.assertTrue(all(len(g) == 8 for g in groups))
def test_different_keys_have_different_fingerprints(self):
_, a = keys.generate()
_, b = keys.generate()
self.assertNotEqual(keys.fingerprint(a), keys.fingerprint(b))
def test_private_key_file_is_owner_only(self):
private, _ = keys.generate()
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "key.pem")
keys.save_private(private, path)
mode = os.stat(path).st_mode & 0o777
self.assertEqual(mode, 0o600, f"expected 0600, got {oct(mode)}")
self.assertEqual(
keys.load_private(path).sign(b"x"), private.sign(b"x")
)
if __name__ == "__main__":
unittest.main()