Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e6f0-a6f6-7b93-9124-55ef584930e2
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
import json
|
|
|
|
import jwt
|
|
import pytest
|
|
|
|
from railiance_clock.core import ClockError, MAX_NS
|
|
from railiance_clock.protocol import TYPE, decode64, encode64, sign, strict_json, verify
|
|
|
|
|
|
def payload(setup):
|
|
_, key, _, transport = setup
|
|
from railiance_clock.protocol import PROFILE
|
|
request = {'profile': PROFILE, 'authority_id': 'railiance01', 'environment': 'test',
|
|
'nonce': encode64(bytes(range(32)))}
|
|
return verify(transport(request), key.public_key(), 'test-key')
|
|
|
|
|
|
def signed_unchecked(value, key, header=None):
|
|
return jwt.encode(value, key, algorithm='ES256',
|
|
headers=header or {'kid': 'test-key', 'typ': TYPE}).encode()
|
|
|
|
|
|
@pytest.mark.parametrize('field,value', [
|
|
('profile', 'railiance-clock-v9'), ('quality', 'unknown'), ('unexpected', 1),
|
|
*[(field, bad) for field in ('receive_unix_ns', 'send_unix_ns', 'error_ns', 'max_age_ns')
|
|
for bad in (str(MAX_NS + 1), '-1', '01', '1.0', '1e3', '1ms', '1\n', 1, True, None)],
|
|
('max_age_ns', '0'), ('nonce', encode64(b'short')),
|
|
])
|
|
def test_signed_invalid_sample_refused(setup, field, value):
|
|
_, key, _, _ = setup
|
|
value_dict = payload(setup); value_dict[field] = value
|
|
# Bypass our sign() validator: this is an authentic but invalid authority statement.
|
|
raw = signed_unchecked(value_dict, key)
|
|
with pytest.raises(ClockError):
|
|
verify(raw, key.public_key(), 'test-key')
|
|
|
|
|
|
@pytest.mark.parametrize('header', [
|
|
{'kid': 'test-key', 'typ': 'wrong'}, {'kid': 'other-key', 'typ': TYPE},
|
|
{'kid': 'test-key', 'typ': TYPE, 'jku': 'https://untrusted.invalid/key'},
|
|
{'kid': 'test-key', 'typ': TYPE, 'crit': ['unknown']},
|
|
])
|
|
def test_authentic_unadmitted_header_refused(setup, header):
|
|
_, key, _, _ = setup
|
|
with pytest.raises(ClockError):
|
|
verify(signed_unchecked(payload(setup), key, header), key.public_key(), 'test-key')
|
|
|
|
|
|
@pytest.mark.parametrize('header', [b'null', b'[]', b'{}', b'{',
|
|
b'{"alg":"ES256","alg":"ES256","kid":"test-key","typ":"railiance-clock-sample-v0.1"}',
|
|
b'{"alg":"none","kid":"test-key","typ":"railiance-clock-sample-v0.1"}'])
|
|
def test_malformed_header_refused(setup, header):
|
|
_, key, _, _ = setup
|
|
raw = sign(payload(setup), key, 'test-key').split(b'.')
|
|
raw[0] = encode64(header).encode()
|
|
with pytest.raises(ClockError):
|
|
verify(b'.'.join(raw), key.public_key(), 'test-key')
|
|
|
|
|
|
def test_modified_uncertainty_invalidates_signature(setup):
|
|
_, key, _, _ = setup
|
|
parts = sign(payload(setup), key, 'test-key').split(b'.')
|
|
changed = json.loads(decode64(parts[1].decode())); changed['error_ns'] = '0'
|
|
parts[1] = encode64(json.dumps(changed).encode()).encode()
|
|
with pytest.raises(ClockError):
|
|
verify(b'.'.join(parts), key.public_key(), 'test-key')
|
|
|
|
|
|
@pytest.mark.parametrize('raw', [b'{"a":1e999}', b'{"a":[-1e999]}', b'{"a":Infinity}'])
|
|
def test_nonfinite_exponent_refused(raw):
|
|
with pytest.raises(ClockError):
|
|
strict_json(raw)
|
|
|
|
|
|
@pytest.mark.parametrize('raw', [b'x' * 8193, b'\xff.abc.def', b'a.b', b'a.b.c.d'])
|
|
def test_invalid_envelope_refused(setup, raw):
|
|
_, key, _, _ = setup
|
|
with pytest.raises(ClockError):
|
|
verify(raw, key.public_key(), 'test-key')
|
|
|
|
|
|
def test_finite_json_numbers_remain_supported():
|
|
assert strict_json(b'{"a":1.25,"b":1e100}') == {'a': 1.25, 'b': 1e100}
|