diff --git a/verify-node.py b/verify-node.py index e08a948..22871da 100644 --- a/verify-node.py +++ b/verify-node.py @@ -14,6 +14,7 @@ import os from pathlib import Path import shutil import socket +import ssl import subprocess import tempfile import time @@ -53,6 +54,7 @@ def validate(config, expectations, exact=True): except ValueError as exc: raise VerificationError('Invalid expected exit IP') from exc require(len(spec['expected_country']) == 2 and spec['expected_country'].isalpha() and spec['expected_country'].isupper(), 'Expected country must be two uppercase letters') + require(spec.get('verification', 'full') in ('full', 'tls'), 'Unsupported verification mode') tag = spec['tag'] require(tag in nodes and tag not in selected, 'Missing/duplicate selected node') node = nodes[tag] @@ -151,6 +153,23 @@ def run_node(config, spec, binary, port, diagnostic_dir): stop(proc) +def run_tls(spec): + """Reach the relay and validate the node certificate; no exit/traffic claim.""" + context = ssl.create_default_context() + with socket.create_connection((spec['host'], spec['port']), timeout=10) as stream: + with context.wrap_socket(stream, server_hostname=spec['sni']) as secure: + protocol = secure.version() + require(protocol in ('TLSv1.2', 'TLSv1.3'), 'Unexpected TLS protocol') + return {'tag': spec['tag'], 'strict_tls': True, 'live': True, + 'relay': spec['host'], 'relay_port': spec['port'], 'sni': spec['sni'], + 'verification': 'tls', 'node_tls': protocol} + + +def run_specs(config, specs, binary, port, folder): + return [run_tls(spec) if spec.get('verification', 'full') == 'tls' + else run_node(config, spec, binary, port, folder) for spec in specs] + + def write_report(path, report): destination = Path(path) require(destination.parent.is_dir() and destination.parent.stat().st_mode & 0o077 == 0, @@ -212,6 +231,7 @@ def main(): specs = [{'tag': candidates[0]['tag'], 'host': qa.get('host'), 'port': qa.get('port'), 'sni': qa.get('sni'), 'uuid': qa.get('uuid'), 'expected_exit': args.expected_exit, 'expected_country': args.expected_country}] + require(bool(args.expectations) or all(s.get('verification', 'full') == 'full' for s in specs), 'Isolated QA requires full verification') tags = validate(config, specs, exact=bool(args.expectations)) results = [] if args.run: @@ -219,8 +239,7 @@ def main(): binary = str(Path(args.binary).resolve()) folder = Path(source).resolve().parent require(folder.stat().st_mode & 0o077 == 0, 'Live diagnostics directory must be private') - for spec in specs: - results.append(run_node(config, spec, binary, args.port, str(folder))) + results = run_specs(config, specs, binary, args.port, str(folder)) report = {'state': 'live_verified' if args.run else 'structure_validated', 'verified_at': int(time.time()) if args.run else None, 'qa_user_id': qa_user_id, 'selected_nodes': tags, 'results': results,