Files
mono/shared/tests/test_http_signatures.py
giles 00efbc2a35 Add unit test coverage for shared pure-logic modules (240 tests)
Track 1.1 of master plan: expand from sexp-only tests to cover
DTOs, HTTP signatures, HMAC auth, URL utilities, Jinja filters,
calendar helpers, config freeze, activity bus registry, parse
utilities, sexp helpers, error classes, and jinja bridge render API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:34:37 +00:00

141 lines
5.4 KiB
Python

"""Tests for RSA key generation and HTTP Signature signing/verification."""
from __future__ import annotations
import json
from shared.utils.http_signatures import (
generate_rsa_keypair,
sign_request,
verify_request_signature,
create_ld_signature,
)
# ---------------------------------------------------------------------------
# Key generation
# ---------------------------------------------------------------------------
class TestKeyGeneration:
def test_generates_pem_strings(self):
private_pem, public_pem = generate_rsa_keypair()
assert isinstance(private_pem, str)
assert isinstance(public_pem, str)
def test_private_key_format(self):
private_pem, _ = generate_rsa_keypair()
assert "BEGIN PRIVATE KEY" in private_pem
assert "END PRIVATE KEY" in private_pem
def test_public_key_format(self):
_, public_pem = generate_rsa_keypair()
assert "BEGIN PUBLIC KEY" in public_pem
assert "END PUBLIC KEY" in public_pem
def test_keys_are_unique(self):
priv1, pub1 = generate_rsa_keypair()
priv2, pub2 = generate_rsa_keypair()
assert priv1 != priv2
assert pub1 != pub2
# ---------------------------------------------------------------------------
# Sign + verify round-trip
# ---------------------------------------------------------------------------
class TestSignVerify:
def test_round_trip_no_body(self):
private_pem, public_pem = generate_rsa_keypair()
headers = sign_request(
private_pem, key_id="https://example.com/users/alice#main-key",
method="GET", path="/users/bob/inbox", host="example.com",
date="Sat, 15 Jun 2025 12:00:00 GMT",
)
assert "Signature" in headers
assert "Date" in headers
assert "Host" in headers
assert "Digest" not in headers
ok = verify_request_signature(
public_pem, headers["Signature"], method="GET",
path="/users/bob/inbox", headers=headers,
)
assert ok is True
def test_round_trip_with_body(self):
private_pem, public_pem = generate_rsa_keypair()
body = b'{"type": "Follow"}'
headers = sign_request(
private_pem, key_id="https://example.com/users/alice#main-key",
method="POST", path="/users/bob/inbox", host="example.com",
body=body, date="Sat, 15 Jun 2025 12:00:00 GMT",
)
assert "Digest" in headers
assert headers["Digest"].startswith("SHA-256=")
ok = verify_request_signature(
public_pem, headers["Signature"], method="POST",
path="/users/bob/inbox", headers=headers,
)
assert ok is True
def test_wrong_key_fails(self):
priv1, _ = generate_rsa_keypair()
_, pub2 = generate_rsa_keypair()
headers = sign_request(
priv1, key_id="key1", method="GET", path="/inbox", host="a.com",
date="Sat, 15 Jun 2025 12:00:00 GMT",
)
ok = verify_request_signature(pub2, headers["Signature"], "GET", "/inbox", headers)
assert ok is False
def test_tampered_path_fails(self):
private_pem, public_pem = generate_rsa_keypair()
headers = sign_request(
private_pem, key_id="key1", method="GET", path="/inbox", host="a.com",
date="Sat, 15 Jun 2025 12:00:00 GMT",
)
ok = verify_request_signature(public_pem, headers["Signature"], "GET", "/tampered", headers)
assert ok is False
def test_tampered_method_fails(self):
private_pem, public_pem = generate_rsa_keypair()
headers = sign_request(
private_pem, key_id="key1", method="GET", path="/inbox", host="a.com",
date="Sat, 15 Jun 2025 12:00:00 GMT",
)
ok = verify_request_signature(public_pem, headers["Signature"], "POST", "/inbox", headers)
assert ok is False
def test_signature_header_contains_key_id(self):
private_pem, _ = generate_rsa_keypair()
headers = sign_request(
private_pem, key_id="https://my.server/actor#main-key",
method="POST", path="/inbox", host="remote.server",
date="Sat, 15 Jun 2025 12:00:00 GMT",
)
assert 'keyId="https://my.server/actor#main-key"' in headers["Signature"]
assert 'algorithm="rsa-sha256"' in headers["Signature"]
# ---------------------------------------------------------------------------
# Linked Data signature
# ---------------------------------------------------------------------------
class TestLDSignature:
def test_creates_ld_signature(self):
private_pem, _ = generate_rsa_keypair()
activity = {"type": "Create", "actor": "https://example.com/users/alice"}
sig = create_ld_signature(private_pem, "https://example.com/users/alice#main-key", activity)
assert sig["type"] == "RsaSignature2017"
assert sig["creator"] == "https://example.com/users/alice#main-key"
assert "signatureValue" in sig
assert "created" in sig
def test_deterministic_canonical(self):
"""Same activity always produces same canonical form (signature differs due to timestamp)."""
private_pem, _ = generate_rsa_keypair()
activity = {"b": 2, "a": 1}
# The canonical form should sort keys
canonical = json.dumps(activity, sort_keys=True, separators=(",", ":"))
assert canonical == '{"a":1,"b":2}'