123 lines
4.3 KiB
Python
123 lines
4.3 KiB
Python
"""End-to-end tests for raycast_api.signing.Signer.
|
|
|
|
Hand-computed canonical/HMAC values for synthetic specs prove the spec
|
|
fields actually drive behaviour (not just defaults).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac as _hmac
|
|
|
|
import pytest
|
|
|
|
from raycast_api.signing import Signer
|
|
from raycast_api.signing_spec import RotRange, SigningSpec
|
|
|
|
|
|
RAYCAST_SPEC = SigningSpec(
|
|
rot_fn_name="Sur",
|
|
signing_fn_name="Nkt",
|
|
rot_ranges=[
|
|
RotRange(0x41, 0x5A, 13),
|
|
RotRange(0x61, 0x7A, 13),
|
|
RotRange(0x30, 0x39, 5),
|
|
],
|
|
)
|
|
|
|
FAKE_SECRET = "DEAD" + "BEEF" * 15
|
|
TS = "1778858809"
|
|
DEVICE = "20eca913cada74f879e6535304f9d44da380c28eb855065c0d71017a3d7c3099"
|
|
|
|
|
|
class TestCanonicalString:
|
|
"""Canonical string composition matches rot(ts) + join + rot(device) + join + rot(sha256(body))."""
|
|
|
|
def test_canonical_string_shape(self) -> None:
|
|
body = b"hello"
|
|
signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
canonical = signer.canonical_string(TS, DEVICE, body)
|
|
body_hex = hashlib.sha256(body).hexdigest()
|
|
from raycast_api.signing.transforms import apply_rot
|
|
|
|
expected = ".".join(
|
|
apply_rot(p, RAYCAST_SPEC.rot_ranges) for p in (TS, DEVICE, body_hex)
|
|
)
|
|
assert canonical == expected
|
|
|
|
|
|
class TestSpecDriven:
|
|
"""Changing a spec field changes the signature — proves nothing is hardcoded."""
|
|
|
|
def test_join_char_change_changes_signature(self) -> None:
|
|
a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
b_spec = SigningSpec(
|
|
rot_fn_name="Sur",
|
|
signing_fn_name="Nkt",
|
|
rot_ranges=RAYCAST_SPEC.rot_ranges,
|
|
join_char=":",
|
|
)
|
|
b = Signer(spec=b_spec, secret=FAKE_SECRET)
|
|
assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign(
|
|
timestamp=TS, device_id=DEVICE, body=b"x"
|
|
)
|
|
|
|
def test_rot_ranges_change_changes_signature(self) -> None:
|
|
a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
b_spec = SigningSpec(
|
|
rot_fn_name="Sur",
|
|
signing_fn_name="Nkt",
|
|
rot_ranges=[
|
|
RotRange(0x41, 0x5A, 1),
|
|
RotRange(0x61, 0x7A, 1),
|
|
RotRange(0x30, 0x39, 1),
|
|
],
|
|
)
|
|
b = Signer(spec=b_spec, secret=FAKE_SECRET)
|
|
assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign(
|
|
timestamp=TS, device_id=DEVICE, body=b"x"
|
|
)
|
|
|
|
def test_secret_change_changes_signature(self) -> None:
|
|
a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
b = Signer(spec=RAYCAST_SPEC, secret="CAFE" + "BABE" * 15)
|
|
assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign(
|
|
timestamp=TS, device_id=DEVICE, body=b"x"
|
|
)
|
|
|
|
def test_body_bytes_change_changes_signature(self) -> None:
|
|
signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
sig_a = signer.sign(timestamp=TS, device_id=DEVICE, body=b'{"k":1}')
|
|
sig_b = signer.sign(timestamp=TS, device_id=DEVICE, body=b'{"k":2}')
|
|
assert sig_a != sig_b
|
|
|
|
def test_timestamp_string_used_verbatim(self) -> None:
|
|
signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
a = signer.sign(timestamp="1778858809", device_id=DEVICE, body=b"x")
|
|
b = signer.sign(timestamp="01778858809", device_id=DEVICE, body=b"x")
|
|
assert a != b
|
|
|
|
|
|
class TestConstruction:
|
|
def test_empty_rot_ranges_rejected(self) -> None:
|
|
bad = SigningSpec(rot_fn_name="x", signing_fn_name="y", rot_ranges=[])
|
|
with pytest.raises(ValueError):
|
|
Signer(spec=bad, secret=FAKE_SECRET)
|
|
|
|
def test_signer_is_reusable_for_concurrent_messages(self) -> None:
|
|
signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
outputs = {
|
|
signer.sign(timestamp=TS, device_id=DEVICE, body=f"{i}".encode())
|
|
for i in range(8)
|
|
}
|
|
assert len(outputs) == 8
|
|
|
|
def test_hmac_uses_utf8_encoded_secret_not_hex_decoded(self) -> None:
|
|
signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET)
|
|
body = b'{"buffer_id":"x"}'
|
|
canonical = signer.canonical_string(TS, DEVICE, body)
|
|
expected = _hmac.new(
|
|
FAKE_SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
|
|
).hexdigest()
|
|
assert signer.sign(timestamp=TS, device_id=DEVICE, body=body) == expected
|