aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/check.py')
-rw-r--r--tests/check.py110
1 files changed, 74 insertions, 36 deletions
diff --git a/tests/check.py b/tests/check.py
index b80bf94..76a23e3 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -769,12 +769,17 @@ def server_links(aup, pure, work):
def server_sealed(aup, pure, work):
- """Sends sealed end to end (--seal to the receiver's --key): through a relay
- that records what passes, every order is applied once and the relay saw
- no item and no field name; one byte changed on the way is refused (400
- seal) and applies nothing, the recorded request sent again applies
- nothing twice, an unsealed delivery is refused (403) by a node that keeps
- its deliveries sealed, and a seal to another key does not open."""
+ """Sends sealed end to end (--seal to the receiver's --key), in one session
+ a wire and authenticated by the sender's own --key: through a relay that
+ records what passes, every order is applied once, the relay saw no item
+ and no field name, and the wire's requests share one session, numbered
+ in order. One byte changed on the way, another number or another
+ sender's key is refused (400 seal) and applies nothing; the recorded
+ request sent again applies nothing twice; an unsealed delivery is
+ refused (403) by a node that keeps its deliveries sealed; a seal to
+ another key does not open. Restarted naming its sender (--sender), the
+ receiver opens that sender's session again unasked, and refuses another
+ node claiming the sender's name with its own key (403 sender)."""
import select
import threading
import time
@@ -807,12 +812,25 @@ def server_sealed(aup, pure, work):
finally:
connection.close()
+ def buy(port, item, count):
+ assert request(port, {'Content-Type': 'application/json'}, json.dumps({'item': item, 'count': count}).encode(),
+ '/inputs/buy')[0] == 200
+
+ def wait_for(condition, seconds=20):
+ deadline = time.time() + seconds
+ while not condition() and time.time() < deadline:
+ time.sleep(0.05)
+ return condition()
+
+ def count():
+ return int(http(port_b, 'GET', '/views/count')[3])
+
key = work / 'seal-receiver.key'
receiver, started = start([pure / 'build/pure-serve', '--module', receiver_module, '--store', work / 'seal-b',
'--key', key, '--sealed-only'])
port_b, public_key = started['port'], started['public_key']
assert len(public_key) == 64 and (key.stat().st_mode & 0o777) == 0o600, (public_key, oct(key.stat().st_mode))
- seen = []
+ seen = [] # (headers, body, status, X-Pure-Error) of each request relayed
class Relay(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
@@ -821,11 +839,11 @@ def server_sealed(aup, pure, work):
body = self.rfile.read(int(self.headers['Content-Length']))
headers = {name: value for name, value in self.headers.items()
if name.lower().startswith('x-pure-') or name.lower() in ('content-type', 'idempotency-key')}
- seen.append((headers, body))
connection = HTTPConnection('127.0.0.1', port_b, timeout=10)
connection.request('POST', self.path, body, headers)
answer = connection.getresponse()
data = answer.read()
+ seen.append((headers, body, answer.status, answer.getheader('X-Pure-Error')))
self.send_response(answer.status)
for name, value in answer.getheaders():
if name.lower().startswith('x-pure-'):
@@ -841,58 +859,78 @@ def server_sealed(aup, pure, work):
relay = ThreadingHTTPServer(('127.0.0.1', 0), Relay)
port_relay = relay.server_address[1]
threading.Thread(target=relay.serve_forever, daemon=True).start()
+ wire = f'ordered=http://127.0.0.1:{port_relay}/inputs/order'
sender, sender_started = start([pure / 'build/pure-serve', '--module', sender_module, '--store', work / 'seal-a',
- '--send', f'ordered=http://127.0.0.1:{port_relay}/inputs/order',
- '--seal', f'ordered={public_key}'])
- port_a = sender_started['port']
- stranger = None
+ '--key', work / 'seal-sender.key', '--send', wire, '--seal', f'ordered={public_key}'])
+ port_a, sender_key = sender_started['port'], sender_started['public_key']
+ stranger = impostor = None
try:
orders, total = 60, 0
for i in range(orders):
- count = i % 7 + 1
- total += count
- assert request(port_a, {'Content-Type': 'application/json'},
- json.dumps({'item': f'secret-{i}', 'count': count}).encode(), '/inputs/buy')[0] == 200
- deadline = time.time() + 20
- while int(http(port_b, 'GET', '/views/count')[3]) < orders and time.time() < deadline:
- time.sleep(0.05)
- assert int(http(port_b, 'GET', '/views/count')[3]) == orders
+ total += i % 7 + 1
+ buy(port_a, f'secret-{i}', i % 7 + 1)
+ assert wait_for(lambda: count() == orders), count()
assert int(http(port_b, 'GET', '/views/total')[3]) == total
- assert seen and all('X-Pure-Seal' in headers for headers, _ in seen), seen[:1]
- assert not any(b'secret' in body or b'item' in body or b'city' in body for _, body in seen)
- headers, body = seen[0]
+ seals = [headers['X-Pure-Seal'].split() for headers, _, _, _ in seen]
+ assert seen and all(len(parts) == 4 and parts[3] == sender_key for parts in seals), seals[:1]
+ assert len({parts[1] for parts in seals}) == 1 # one session
+ numbers = [int(parts[2]) for parts in seals]
+ assert numbers == list(range(len(numbers))), numbers
+ assert all(status == 200 for _, _, status, _ in seen)
+ assert not any(b'secret' in body or b'item' in body or b'city' in body for _, body, _, _ in seen)
+ headers, body, _, _ = seen[0]
+ suite, enc, n, _ = headers['X-Pure-Seal'].split()
changed = bytearray(body)
changed[len(changed) // 2] ^= 1
assert request(port_b, headers, bytes(changed)) == (400, 'seal')
+ renumbered = dict(headers, **{'X-Pure-Seal': f'{suite} {enc} {int(n) + 1} {sender_key}'})
+ assert request(port_b, renumbered, body) == (400, 'seal')
+ other_sender = f'{suite} {enc} {n} {public_key}'
+ assert request(port_b, dict(headers, **{'X-Pure-Seal': other_sender}), body) == (400, 'seal')
assert request(port_b, headers, body)[0] == 200 # heard before: applied once
plain = {name: value for name, value in headers.items() if name != 'X-Pure-Seal'}
assert request(port_b, plain, json.dumps({'item': 'plain', 'count': 1, 'to': {'city': 'x'}}).encode()) == \
(403, 'unsealed')
+ assert count() == orders
# A sender sealing to another node's key: the receiver cannot open it.
other, other_started = start([pure / 'build/pure-serve', '--module', receiver_module, '--store', work / 'seal-c',
'--key', work / 'seal-other.key'])
other.terminate()
other.wait()
stranger, stranger_started = start([pure / 'build/pure-serve', '--module', sender_module, '--store',
- work / 'seal-d', '--send', f'ordered=http://127.0.0.1:{port_relay}/inputs/order',
- '--seal', f'ordered={other_started["public_key"]}'])
+ work / 'seal-d', '--send', wire, '--seal',
+ f'ordered={other_started["public_key"]}'])
before = len(seen)
- assert request(stranger_started['port'], {'Content-Type': 'application/json'},
- json.dumps({'item': 'stranger', 'count': 5}).encode(), '/inputs/buy')[0] == 200
- deadline = time.time() + 5
- while len(seen) == before and time.time() < deadline:
- time.sleep(0.05)
- time.sleep(0.2)
- assert len(seen) > before and int(http(port_b, 'GET', '/views/count')[3]) == orders
- # The same key file gives the same public key after a restart.
+ buy(stranger_started['port'], 'stranger', 5)
+ assert wait_for(lambda: len(seen) > before, 5)
+ assert seen[before][2:] == (400, 'seal') and len(seen[before][0]['X-Pure-Seal'].split()) == 3
+ assert count() == orders
+ # Restarted naming its sender: the same key opens the sender's session
+ # again, and another node claiming the sender's name is refused.
+ source = seen[0][0]['X-Pure-Source']
receiver.terminate()
receiver.wait()
receiver, again = start([pure / 'build/pure-serve', '--module', receiver_module, '--store', work / 'seal-b',
- '--key', key, '--sealed-only'])
+ '--key', key, '--sender', f'{source}={sender_key}'])
+ port_b = again['port']
assert again['public_key'] == public_key
- return {'orders': orders, 'relayed': before, 'suite': seen[0][0]['X-Pure-Seal']}
+ before = len(seen)
+ buy(port_a, 'after', 2)
+ assert wait_for(lambda: count() == orders + 1), count()
+ assert seen[-1][0]['X-Pure-Seal'].split()[1] == enc # the same session, opened again unasked
+ impostor, impostor_started = start([pure / 'build/pure-serve', '--module', sender_module, '--store',
+ work / 'seal-e', '--key', work / 'seal-impostor.key', '--send', wire,
+ '--seal', f'ordered={public_key}'])
+ before = len(seen)
+ buy(impostor_started['port'], 'impostor', 9)
+ assert wait_for(lambda: any(h['X-Pure-Seal'].split()[3] == impostor_started['public_key']
+ for h, _, _, _ in seen[before:]), 5)
+ claimed = [entry for entry in seen[before:] if entry[0]['X-Pure-Seal'].split()[3] != sender_key]
+ assert claimed and claimed[0][0]['X-Pure-Source'] == source and claimed[0][2:] == (403, 'sender'), claimed[:1]
+ assert count() == orders + 1
+ return {'orders': orders, 'relayed': len(seen), 'suite': suite, 'sessions': 1}
finally:
- for process in (sender, receiver, stranger):
+ for process in (sender, receiver, stranger, impostor):
if process:
process.terminate()
process.wait()