feat: client-side TLS for encrypted client connections

Accept TLS-encrypted connections from IRC clients. Auto-generates a
self-signed EC P-256 listener certificate (bouncer.pem) when no custom
cert is provided. Remove CTCP response items from roadmap (stealth by
design -- router already suppresses all CTCP except ACTION).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 18:47:20 +01:00
parent bfcebad6dd
commit bf4a589fc5
12 changed files with 400 additions and 25 deletions

View File

@@ -11,8 +11,10 @@ from bouncer.cert import (
delete_cert,
fingerprint,
generate_cert,
generate_listener_cert,
has_cert,
list_certs,
listener_cert_path,
)
@@ -22,6 +24,41 @@ def data_dir(tmp_path: Path) -> Path:
return tmp_path
class TestGenerateListenerCert:
def test_creates_pem_with_cn_bouncer(self, data_dir: Path) -> None:
from cryptography import x509 as x509_mod
from cryptography.x509.oid import NameOID
pem = generate_listener_cert(data_dir)
assert pem.is_file()
assert pem == listener_cert_path(data_dir)
cert_data = pem.read_bytes()
cert_obj = x509_mod.load_pem_x509_certificate(cert_data)
cn = cert_obj.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
assert cn == "bouncer"
content = pem.read_text()
assert "BEGIN CERTIFICATE" in content
assert "BEGIN PRIVATE KEY" in content
mode = pem.stat().st_mode & 0o777
assert mode == 0o600
def test_idempotent(self, data_dir: Path) -> None:
pem1 = generate_listener_cert(data_dir)
fp1 = fingerprint(pem1)
mtime1 = pem1.stat().st_mtime
pem2 = generate_listener_cert(data_dir)
fp2 = fingerprint(pem2)
mtime2 = pem2.stat().st_mtime
assert pem1 == pem2
assert fp1 == fp2
assert mtime1 == mtime2 # file not regenerated
class TestCertPath:
def test_standard_path(self, data_dir: Path) -> None:
p = cert_path(data_dir, "libera", "fabesune")