Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131import jsonimport osfrom pathlib import Pathimport socketimport sslimport subprocessimport tempfileimport threading
root = Path(__file__).resolve().parents[1]with tempfile.TemporaryDirectory(prefix="zttp-tls-clock-") as directory: directory = Path(directory) cert, key = directory / "cert.pem", directory / "key.pem" subprocess.run([ "openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", str(key), "-out", str(cert), "-days", "1", "-subj", "/CN=localhost", "-addext", "subjectAltName=DNS:localhost", ], check=True, capture_output=True) context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(cert, key) listener = socket.socket() listener.bind(("127.0.0.1", 0)) listener.listen() listener.settimeout(0.2) port = listener.getsockname()[1] stop = threading.Event()
barrier = threading.Barrier(2) handlers = []
def handle(connection): with connection: connection.settimeout(5) try: with context.wrap_socket(connection, server_side=True) as tls: request = b"" while b"\r\n\r\n" not in request: chunk = tls.recv(4096) if not chunk: break request += chunk if request.startswith(b"GET /parallel "): barrier.wait(timeout=3) tls.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok") except (ssl.SSLError, OSError, threading.BrokenBarrierError): pass
def serve(): while not stop.is_set(): try: connection, _ = listener.accept() except socket.timeout: continue handler = threading.Thread(target=handle, args=(connection,)) handlers.append(handler) handler.start()
server = threading.Thread(target=serve) server.start() source = directory / "test.zig" source.write_text('''const std = @import("std");const HttpTransport = @import("transport").Client;
fn fetchParallel(transport: *HttpTransport) !void { var result = try transport.fetch(.{ .url = PARALLEL_URL }); defer result.deinit(std.testing.allocator); try std.testing.expectEqual(std.http.Status.ok, result.status);}
test "TLS uses current time after certificate rotation and still checks hostnames" { const allocator = std.testing.allocator; var threaded: std.Io.Threaded = .init(allocator, .{}); defer threaded.deinit(); const io = threaded.io(); var cold = HttpTransport.init(io, allocator); defer cold.deinit(); try std.testing.expectError(error.TlsInitializationFailed, cold.fetch(.{ .url = URL, .resolved_connection = .{ .dial_host = "127.0.0.1", .logical_host = "localhost" }, })); var transport = HttpTransport.init(io, allocator); defer transport.deinit(); transport.keep_alive = false; transport.dead_connection_attempts = 0; const now = std.Io.Clock.real.now(io); try transport.http_client.ca_bundle.addCertsFromFilePathAbsolute(allocator, io, now, CERT); transport.http_client.now = .{ .nanoseconds = now.nanoseconds - 2 * std.time.ns_per_day }; var normal = try transport.fetch(.{ .url = URL }); defer normal.deinit(allocator); try std.testing.expectEqual(std.http.Status.ok, normal.status); try std.testing.expectEqualStrings("ok", normal.body);
transport.http_client.now = .{ .nanoseconds = now.nanoseconds - 2 * std.time.ns_per_day }; var resolved = try transport.fetch(.{ .url = URL, .resolved_connection = .{ .dial_host = "127.0.0.1", .logical_host = "localhost" }, }); defer resolved.deinit(allocator); try std.testing.expectEqual(std.http.Status.ok, resolved.status); try std.testing.expectEqualStrings("ok", resolved.body); try std.testing.expectError(error.TlsInitializationFailed, transport.fetch(.{ .url = BAD_URL })); try std.testing.expectError(error.TlsInitializationFailed, transport.fetch(.{ .url = BAD_URL, .resolved_connection = .{ .dial_host = "127.0.0.1", .logical_host = "127.0.0.1" }, })); var first = try io.concurrent(fetchParallel, .{&transport}); defer first.cancel(io) catch {}; try fetchParallel(&transport); try first.await(io);}'''.replace("CERT", json.dumps(str(cert))) .replace("PARALLEL_URL", json.dumps(f"https://localhost:{port}/parallel")) .replace("BAD_URL", json.dumps(f"https://127.0.0.1:{port}/")) .replace("URL", json.dumps(f"https://localhost:{port}/"))) build_options = directory / "build_options.zig" build_options.write_text('pub const version = "test";\n') try: subprocess.run([ os.environ.get("ZIG", "zig"), "test", "--dep", "transport", f"-Mroot={source}", "--dep", "build_options", f"-Mtransport={root / 'src/client.zig'}", f"-Mbuild_options={build_options}", "-lc", ], check=True) finally: stop.set() server.join() for handler in handlers: handler.join() listener.close()