test/testssloptions.py
Rama master · réplica de lectura del repositorio original.
#!/usr/bin/env python3
#
# GnuTLS SSLOptions integration tests for CUPS.
# Copyright (c) 2026 by OpenPrinting.
# Licensed under Apache License v2.0. See LICENSE for details.
#
# Run after building with --with-tls=gnutls: python3 test/testssloptions.py
# Requires a C compiler, Python 3 with TLS 1.3 support, and the openssl command.
# All certificates, configuration, and connections are local to this test.
import os
import shlex
import socket
import ssl
import subprocess
import tempfile
import threading
from pathlib import Path
root = Path(__file__).resolve().parent.parent
if "#define HAVE_GNUTLS 1" not in (root / "config.h").read_text():
raise SystemExit("Configure CUPS with --with-tls=gnutls before running this test.")
failures = 0
with tempfile.TemporaryDirectory(prefix="cups-ssloptions-") as d:
p = Path(d)
(p / "client.c").write_text(r"""
#include "cups/cups.h"
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
char security[1024]; /* Negotiated TLS settings */
http_t *http; /* Connection to the test server */
if (argc != 2)
return (2);
http = httpConnect2("localhost", atoi(argv[1]), NULL, AF_INET,
HTTP_ENCRYPTION_ALWAYS, 1, 2000, NULL);
if (!http)
{
fprintf(stderr, "%s\n", cupsGetErrorString());
return (1);
}
puts(httpGetSecurity(http, security, sizeof(security)));
httpClose(http);
return (0);
}
""")
subprocess.run(
shlex.split(os.environ.get("CC", "cc"))
+ [
"-I" + str(root),
str(p / "client.c"),
"-L" + str(root / "cups"),
"-Wl,-rpath," + str(root / "cups"),
"-lcups",
"-o",
str(p / "client"),
],
check=True,
)
subprocess.run(
[
os.environ.get("OPENSSL", "openssl"),
"req",
"-x509",
"-newkey",
"rsa:2048",
"-nodes",
"-keyout",
str(p / "key.pem"),
"-out",
str(p / "cert.pem"),
"-days",
"1",
"-subj",
"/CN=localhost",
],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
cases = [
(
"missing SYSTEM honors MaxTLS1.2",
"",
"MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_3,
True,
"TLS/1.2",
),
(
"missing SYSTEM rejects TLS1.3-only server",
"",
"MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_3,
ssl.TLSVersion.TLSv1_3,
False,
None,
),
(
"missing SYSTEM honors MinTLS1.3",
"",
"MinTLS1.3",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_2,
False,
None,
),
(
"NoSystem remains functional",
"",
"NoSystem MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_3,
True,
"TLS/1.2",
),
(
"configured SYSTEM remains functional",
"[priorities]\nSYSTEM = NORMAL\n",
"MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_3,
True,
"TLS/1.2",
),
(
"configured SYSTEM keeps cipher restriction",
"[priorities]\nSYSTEM = NORMAL:-AES-128-GCM\n",
"MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_2,
False,
"GCM",
),
(
"NoSystem bypasses named cipher restriction",
"[priorities]\nSYSTEM = NORMAL:-AES-128-GCM\n",
"NoSystem MinTLS1.2 MaxTLS1.2",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_2,
True,
"GCM",
),
(
"missing SYSTEM honors DenyCBC",
"",
"MinTLS1.2 MaxTLS1.2 DenyCBC",
ssl.TLSVersion.TLSv1_2,
ssl.TLSVersion.TLSv1_2,
False,
"CBC",
),
]
for name, policy, options, minimum, maximum, success, expected in cases:
(p / "gnutls.conf").write_text(policy)
(p / "client.conf").write_text("SSLOptions " + options + "\n")
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.minimum_version = minimum
ctx.maximum_version = maximum
ctx.load_cert_chain(p / "cert.pem", p / "key.pem")
if expected == "CBC":
ctx.set_ciphers("ECDHE-RSA-AES128-SHA256")
if expected == "GCM":
ctx.set_ciphers("ECDHE-RSA-AES128-GCM-SHA256")
listener = socket.socket()
listener.bind(("127.0.0.1", 0))
listener.listen()
listener.settimeout(5)
port = listener.getsockname()[1]
def serve(listener, ctx):
try:
conn, _ = listener.accept()
with conn:
conn.settimeout(4)
with ctx.wrap_socket(conn, server_side=True) as tls:
tls.recv(1)
except (ssl.SSLError, OSError):
pass
finally:
listener.close()
thread = threading.Thread(target=serve, args=(listener, ctx))
thread.start()
env = dict(
os.environ,
GNUTLS_SYSTEM_PRIORITY_FILE=str(p / "gnutls.conf"),
CUPS_SYSCONFIG=d,
CUPS_USERCONFIG=d,
)
r = subprocess.run(
[str(p / "client"), str(port)],
env=env,
check=False,
capture_output=True,
text=True,
timeout=8,
)
thread.join(6)
if success:
passed = r.returncode == 0 and expected in r.stdout
else:
passed = r.returncode == 1 and "TLS" in r.stderr
failures += not passed
print(
("PASS" if passed else "FAIL"),
name,
"=>",
(r.stdout + r.stderr).strip(),
flush=True,
)
print("Failures:", failures)
raise SystemExit(bool(failures))