mirror of
https://github.com/progval/irctest.git
synced 2025-04-05 06:49:47 +00:00
Add TLS certificate check tests for clients.
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
import ssl
|
||||
import time
|
||||
import socket
|
||||
import tempfile
|
||||
import unittest
|
||||
import functools
|
||||
import collections
|
||||
@ -11,6 +13,7 @@ from . import client_mock
|
||||
from . import authentication
|
||||
from .irc_utils import capabilities
|
||||
from .irc_utils import message_parser
|
||||
from .exceptions import ConnectionClosed
|
||||
from .specifications import Specifications
|
||||
|
||||
class _IrcTestCase(unittest.TestCase):
|
||||
@ -107,9 +110,23 @@ class BaseClientTestCase(_IrcTestCase):
|
||||
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server.bind(('', 0)) # Bind any free port
|
||||
self.server.listen(1)
|
||||
def acceptClient(self):
|
||||
def acceptClient(self, tls_cert=None, tls_key=None):
|
||||
"""Make the server accept a client connection. Blocking."""
|
||||
(self.conn, addr) = self.server.accept()
|
||||
if tls_cert is None and tls_key is None:
|
||||
pass
|
||||
else:
|
||||
assert tls_cert and tls_key, \
|
||||
'tls_cert must be provided if and only if tls_key is.'
|
||||
with tempfile.NamedTemporaryFile('at') as certfile, \
|
||||
tempfile.NamedTemporaryFile('at') as keyfile:
|
||||
certfile.write(tls_cert)
|
||||
certfile.seek(0)
|
||||
keyfile.write(tls_key)
|
||||
keyfile.seek(0)
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
context.load_cert_chain(certfile=certfile.name, keyfile=keyfile.name)
|
||||
self.conn = context.wrap_socket(self.conn, server_side=True)
|
||||
self.conn_file = self.conn.makefile(newline='\r\n',
|
||||
encoding='utf8')
|
||||
|
||||
@ -127,6 +144,8 @@ class BaseClientTestCase(_IrcTestCase):
|
||||
and returns this message."""
|
||||
while True:
|
||||
line = self.getLine(*args)
|
||||
if not line:
|
||||
raise ConnectionClosed()
|
||||
msg = message_parser.parse_message(line)
|
||||
if not filter_pred or filter_pred(msg):
|
||||
return msg
|
||||
@ -141,12 +160,13 @@ class BaseClientTestCase(_IrcTestCase):
|
||||
|
||||
class ClientNegociationHelper:
|
||||
"""Helper class for tests handling capabilities negociation."""
|
||||
def readCapLs(self, auth=None):
|
||||
def readCapLs(self, auth=None, tls_config=None):
|
||||
(hostname, port) = self.server.getsockname()
|
||||
self.controller.run(
|
||||
hostname=hostname,
|
||||
port=port,
|
||||
auth=auth,
|
||||
tls_config=tls_config,
|
||||
)
|
||||
self.acceptClient()
|
||||
m = self.getMessage()
|
||||
|
@ -2,13 +2,7 @@ import ssl
|
||||
import time
|
||||
import socket
|
||||
from .irc_utils import message_parser
|
||||
|
||||
|
||||
class NoMessageException(AssertionError):
|
||||
pass
|
||||
|
||||
class ConnectionClosed(Exception):
|
||||
pass
|
||||
from .exceptions import NoMessageException, ConnectionClosed
|
||||
|
||||
class ClientMock:
|
||||
def __init__(self, name, show_io):
|
||||
|
143
irctest/client_tests/test_tls.py
Normal file
143
irctest/client_tests/test_tls.py
Normal file
@ -0,0 +1,143 @@
|
||||
from irctest import tls
|
||||
from irctest import cases
|
||||
from irctest.exceptions import ConnectionClosed
|
||||
from irctest.irc_utils.message_parser import Message
|
||||
|
||||
BAD_CERT = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOd2PGU3RNwhMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTYwNzIwMDg0NjIwWhcNMTYwODE5MDg0NjIwWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAqo1Xu+f7UmdtNPTNPLxfILf9j/kGNNHkfqVjMHXc9rNL+JoMQ3eTdy7x
|
||||
BqrWmiCHNOBAeES9anF+2SAd0LiOD2gO6h8R/+s9ftNCmZJa6kCGLX1uf5rp85aD
|
||||
YbqbalgQS6PtRQZHU7+XOtW/YOolpG/2omgQmZMLyEQKNseQ4VQnuIYZoJRmXLsK
|
||||
eyLgWNbpz0CsLljEziTsOLYnX9n8T469+EWgFQIvWpd/jirNTSPGTc3HVRs9g7dy
|
||||
fZNi7b0jjb0qhDCOR0Kvyl9I0ANz4uEX+z/ZYfsZFU4xV7vxrDNp4gSAu8bW5JQy
|
||||
/jJOsGL/9pXthCsXxY0S/6PQK70DOQIDAQABo1AwTjAdBgNVHQ4EFgQUME3YXimi
|
||||
RNBg6V0SWY/417o/2zIwHwYDVR0jBBgwFoAUME3YXimiRNBg6V0SWY/417o/2zIw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAPljmzqGfc4wcdkTFSSBg
|
||||
BQzq/nUn16cTtRYaOOxAxCK4VFWY9MxxlcVlDUx1VtUPBJaUNqJ+xdIIdwBOH3O/
|
||||
jwDIQMRVlXwolTZvXw/xoatpb20644bltvftJ+6TpXY6z673+5Pu7b8FjNpZd/qs
|
||||
5MGsgkAGkNN6hVvOqVASMqaO5vv7UgrL1Dh4R//ADBhonBwEP4Ykz+Y8gDVXlfSx
|
||||
ak4YDQfuB2+M8Y3Y9PgKNZclYEacXwV/ZIxfm7vkOPlKOEeyi9+PzCEJINWnoE08
|
||||
HNsJTz9ijzsHiac6Xw07FwOBQ/3LRngfcgEOqS6W8vTC4vCkWb88mbLI4CUwi+n7
|
||||
dw==
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
BAD_KEY = """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqjVe75/tSZ200
|
||||
9M08vF8gt/2P+QY00eR+pWMwddz2s0v4mgxDd5N3LvEGqtaaIIc04EB4RL1qcX7Z
|
||||
IB3QuI4PaA7qHxH/6z1+00KZklrqQIYtfW5/munzloNhuptqWBBLo+1FBkdTv5c6
|
||||
1b9g6iWkb/aiaBCZkwvIRAo2x5DhVCe4hhmglGZcuwp7IuBY1unPQKwuWMTOJOw4
|
||||
tidf2fxPjr34RaAVAi9al3+OKs1NI8ZNzcdVGz2Dt3J9k2LtvSONvSqEMI5HQq/K
|
||||
X0jQA3Pi4Rf7P9lh+xkVTjFXu/GsM2niBIC7xtbklDL+Mk6wYv/2le2EKxfFjRL/
|
||||
o9ArvQM5AgMBAAECggEAPwbqxDMvij1Uezx4WBiY4wN7fegeJgjm8vJ1nGQCG10Z
|
||||
Fy7+lzQqV+IOClO56M1aiezRhmCIyzxUDzMyMX7yaLkgwd5njXbGjAbQVuZiGK1t
|
||||
qIPxANEj4fPea5BFfOA8bWeP+HEgjM+BuKljBxKghIsnzs68S7SupvyV9bZ8UPho
|
||||
uGZdgFfwzJlYTrjuZg1xz3KSsjDC/MrTQ3QldYlqMLjFooZH74j+vh/HAesEUu+E
|
||||
aNMw0sAYi70F5xqAjLjEdNxKz05fGEkh1PPeohe2hF+vCDMMf/Si2PIbA44Z1Sod
|
||||
0cFCE1zQhuJ1yOLQJwQ7wgEh9/Zz+M4L2BLB7P5OPQKBgQDgu1I1kqv/1EGTd36v
|
||||
IQbYr1MVLzqWVXCTd7wdOcWIO538veQ/n/ED183I7xDt3GCBvXIwdoC0e4C9ZCAl
|
||||
mjFUAawWDeQ0Ficbop51v1R/b/iAxQaIq1StUKrahZO0jjyH96CHISSUNlEWoRE3
|
||||
Zh9F+PQ7tz77swn+q4oTeiUcBwKBgQDCSDVBZHO5mUTeVlyA+G93l/AwRxihWnGl
|
||||
5yF/ybqxrf27MywhN7fhZCvNtcYfWTbJOh6fwnzcj0YcrPQFJ2QYt9R+tSLhkXPs
|
||||
X5aXHH9MQ+lItUQ0rmSv2D8MpIulwmUpZIoCKMs17Pb81EU4NSFwa2eJmdezAyHW
|
||||
T9LlQReWvwKBgDqbP0YvWOGftfZCLGx5fXKWzmDw7yNzZqdei1VH0qbDfWEDGHor
|
||||
OMxaxBTJm62cUiKjiBrxXIE00A8UBHop6wFQalNaDhAzUsGXOCHW4q9VQQY724da
|
||||
vvtv1Q6l1S46Bbkjr95tmz93ps/y8y1yWWeDFBZapHc5arrae2i26uSTAoGACEhf
|
||||
zNvleyInp3rzEqSEzAp0OPqu+CIM+k+yQ+prxStvx81Usk3XzwogO/Ll8WwyQ73w
|
||||
lEsMW7LYAFz3Qkj9oXgk3QoH5Kn40Tj6CJM0ciHrDih8MerFbCHB/l39fiGdgnhA
|
||||
0fq/PxtNJFZAZTcOp+ZMUbd3VLBrfuGEUjXGNa0CgYEAqtwfoXxUIPWfZ7ezNX2m
|
||||
Cbnl6JGjjYoDgohr8lHcpIc+dVChLopHayUxECWIU03Todlrn2/KNwjUKtovSsty
|
||||
h4WuPDAI4yh24GjaCZYGR5xcqPCy5CNjMLxdA7HsP+Gcr3eY5XS7noBrbC6IaA0j
|
||||
9E+dB63zMDFOnC4UVg5rD28=
|
||||
-----END PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
GOOD_FINGERPRINT = 'E1EE6DE2DBC0D43E3B60407B5EE389AEC9D2C53178E0FB14CD51C3DFD544AA2B'
|
||||
GOOD_CERT = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAKtD9XMC1R0vMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTYwNzIwMDg0NjU0WhcNMTYwODE5MDg0NjU0WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA0CZDv/ny3caI0a2r7P9qH3eyPYxd+Vz5i6YzCrVIqpq9PeWL9zf9IQoM
|
||||
4TAOMS9VQOCq3HsSm0YKRC9tYflmBb03rriUExsFyd4CgAqKjYNJDrTWX23j+g5T
|
||||
KHF+gYhQlIljQcvX1JVMHThS1nYCz6tnbBUsKrncW9LxnR0PydL+i8jS2SkPhe/z
|
||||
t/VfWsTigSzz7xVEA54ow4sYbXVx1D6CNsjccTq/hfbRGkBWvYDZt7s/bj2h445Y
|
||||
B1uVuIQygySkwGQMnNALZMUhiAsuCyV7PNNleGbIPUd0LExD6OQPVchof+tdiXq7
|
||||
ndLsVv6Ufh1DhPDXtn9891sOkoj2cQIDAQABo1AwTjAdBgNVHQ4EFgQUtsTGgJ3E
|
||||
rRxqF0doikKnpvDr/dswHwYDVR0jBBgwFoAUtsTGgJ3ErRxqF0doikKnpvDr/dsw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAWT2/0/ONY6XflNqGvn0i
|
||||
XfB72FKIttuxMPiFKoV4czD2JWFZJ6eSTS+9NOUFPOzJfakl/F3a5Vy41hAF35o3
|
||||
9N0jQt1ixkxi/BPEW2Twst4smnYgKHS4Lke8/EPn2gemxKEz7lpwICR/bFgOFIR5
|
||||
OvQ2HQ+16yi8TsbB3QTUyVuixhYawlOpTtmDg9hho74+VA1oJ5bpx2maS2OTH35O
|
||||
C458H4VAVNxtOIZF/zUhD8TEuTIElZtzJpghB9MdblaV8vs1fe2+ZWMXzSKOKj12
|
||||
nGGz249IcunUMzjOzk6w7sVSZRWkwtwov5DsyaeW2+raig+NfF7sLECI57GWakVJ
|
||||
Pg==
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
GOOD_KEY = """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQJkO/+fLdxojR
|
||||
ravs/2ofd7I9jF35XPmLpjMKtUiqmr095Yv3N/0hCgzhMA4xL1VA4KrcexKbRgpE
|
||||
L21h+WYFvTeuuJQTGwXJ3gKACoqNg0kOtNZfbeP6DlMocX6BiFCUiWNBy9fUlUwd
|
||||
OFLWdgLPq2dsFSwqudxb0vGdHQ/J0v6LyNLZKQ+F7/O39V9axOKBLPPvFUQDnijD
|
||||
ixhtdXHUPoI2yNxxOr+F9tEaQFa9gNm3uz9uPaHjjlgHW5W4hDKDJKTAZAyc0Atk
|
||||
xSGICy4LJXs802V4Zsg9R3QsTEPo5A9VyGh/612Jerud0uxW/pR+HUOE8Ne2f3z3
|
||||
Ww6SiPZxAgMBAAECggEAMeb6lyv1bfYLFznr3gXeC21G7jqYzQ/dQ/20fvy3Ty+J
|
||||
7yz5QWvK5ADk1ZgPzvrqFYPHctSOwWspSu+T6clBDF8w2lKmLW5tFNiFAO2GCidP
|
||||
fJceTgKqhWipxyhui9+Cchn+Eegs9mpUtSyrr37bba5KPT9WN2gXzGvmQSSWhGwC
|
||||
ewrJjh+HBpS3B006kqD9kOdgJYlmTayOaqD9WOH/ppFY8zNopEx27qqEph6WOA7W
|
||||
84oUqm4JNabyXz7tRa/YsBUUqKmlBgxp0sKu5NttBPUi1+LpumjWIRj6M85NHliK
|
||||
lCJgEkNriXMQ7joS0n0ZK+1cuKNr7hiGKwDoNUsfgQKBgQD0uB8KAgIjkgA+FRpr
|
||||
cIQ35INPdkN8HkzZdg19nrQTwuh2Jo4SSbfm57KwyZBMnP2kHP6CMZNCycVXXj+r
|
||||
1jo5ZFdAyz+MgUdTmTBXV/wH1YS6Cbp4KOv64eYT3/3cGUE5aBG0U5CqNaYq0XT7
|
||||
CxPiF+pDRVDDQk+rBFz9gCKeWwKBgQDZvpd2DkVWvL0sk6pu9taPutKN145USzOn
|
||||
j3SceLzL8Lu5nEfkW9O9EG0APVf8M7iq4JYF5Yzam94/pbLpSZHzYuEgNt1ee7TG
|
||||
7cnxsKxQ9PDrvbElJGx0j0XRwG/CU3RJeDDmaUyosKjcYMhJujDk05Vm3RTLsii2
|
||||
QiCvu4jwIwKBgQCXbl/2p2t/a1cvE4v3s/Z9R7BhuYLlCTLw1fZfJ5ezKscCZbVA
|
||||
Z9Ge1v1iHDho0DS8Gxz6n4bKq2SsPawUv0nkPc0oUR0P6ueiOYcKZW2Vw3CQVnjG
|
||||
5juwUZ0360GBszcDOPzLo3I/gVdD470Jo784Byh1XC0vxpbZ8qdATswdRQKBgDad
|
||||
XXQZBD9LO8/QgfEvLIYEgAdfx61Q53Xhv4f3qLMmgI9/qXCXr7Y+RnjG6iix+GGz
|
||||
zy1PdFLowYgJUaS99UOsy3a/DCtEsAUtY3ehrrbnmP4oKCR+zE04GnUP5XhCYmqD
|
||||
IRDJ3JZ7KP+Nru7/KoBaqaCRV0P4PcnpMDWjvictAoGAWTFD2h/tsSWyHN2OyyBG
|
||||
wmfusGVYB23RgQzXiLdlZOwWHZGON9dKEc9Pq6ddRArO01ewAKkcfieaLLpgb67C
|
||||
Sw3oB/NsbUMkKze1zwXs9e2vcPt42vnRuQ75jU7Pb9p2NHpAdA4K/3CV00QzGA+e
|
||||
El9iqRlAhgqaXc4Iz/Zxxhs=
|
||||
-----END PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
class TlsTestCase(cases.BaseClientTestCase):
|
||||
def testTrustedCertificate(self):
|
||||
tls_config = tls.TlsConfig(
|
||||
enable=True,
|
||||
trusted_fingerprints=[GOOD_FINGERPRINT])
|
||||
(hostname, port) = self.server.getsockname()
|
||||
self.controller.run(
|
||||
hostname=hostname,
|
||||
port=port,
|
||||
auth=None,
|
||||
tls_config=tls_config,
|
||||
)
|
||||
self.acceptClient(tls_cert=GOOD_CERT, tls_key=GOOD_KEY)
|
||||
m = self.getMessage()
|
||||
|
||||
def testUntrustedCertificate(self):
|
||||
tls_config = tls.TlsConfig(
|
||||
enable=True,
|
||||
trusted_fingerprints=[GOOD_FINGERPRINT])
|
||||
(hostname, port) = self.server.getsockname()
|
||||
self.controller.run(
|
||||
hostname=hostname,
|
||||
port=port,
|
||||
auth=None,
|
||||
tls_config=tls_config,
|
||||
)
|
||||
self.acceptClient(tls_cert=BAD_CERT, tls_key=BAD_KEY)
|
||||
with self.assertRaises(ConnectionClosed):
|
||||
m = self.getMessage()
|
@ -2,6 +2,7 @@ import os
|
||||
import subprocess
|
||||
|
||||
from irctest import authentication
|
||||
from irctest.basecontrollers import NotImplementedByController
|
||||
from irctest.basecontrollers import BaseClientController, DirectoryBasedController
|
||||
|
||||
TEMPLATE_CONFIG = """
|
||||
@ -9,9 +10,14 @@ supybot.directories.conf: {directory}/conf
|
||||
supybot.directories.data: {directory}/data
|
||||
supybot.directories.migrations: {directory}/migrations
|
||||
supybot.log.stdout.level: {loglevel}
|
||||
|
||||
supybot.networks: testnet
|
||||
supybot.networks.testnet.servers: {hostname}:{port}
|
||||
supybot.networks.testnet.ssl: False
|
||||
|
||||
supybot.protocols.ssl.verifyCertificates: True
|
||||
supybot.networks.testnet.ssl: {enable_tls}
|
||||
supybot.networks.testnet.ssl.serverFingerprints: {trusted_fingerprints}
|
||||
|
||||
supybot.networks.testnet.sasl.username: {username}
|
||||
supybot.networks.testnet.sasl.password: {password}
|
||||
supybot.networks.testnet.sasl.ecdsa_key: {directory}/ecdsa_key.pem
|
||||
@ -30,7 +36,7 @@ class LimnoriaController(BaseClientController, DirectoryBasedController):
|
||||
with self.open_file('conf/users.conf'):
|
||||
pass
|
||||
|
||||
def run(self, hostname, port, auth):
|
||||
def run(self, hostname, port, auth, tls_config):
|
||||
# Runs a client with the config given as arguments
|
||||
assert self.proc is None
|
||||
self.create_config()
|
||||
@ -51,6 +57,8 @@ class LimnoriaController(BaseClientController, DirectoryBasedController):
|
||||
username=auth.username if auth else '',
|
||||
password=auth.password if auth else '',
|
||||
mechanisms=mechanisms.lower(),
|
||||
enable_tls=tls_config.enable if tls_config else 'False',
|
||||
trusted_fingerprints=' '.join(tls_config.trusted_fingerprints) if tls_config else '',
|
||||
))
|
||||
self.proc = subprocess.Popen(['supybot',
|
||||
os.path.join(self.directory, 'bot.conf')])
|
||||
|
@ -3,6 +3,7 @@ import tempfile
|
||||
import subprocess
|
||||
|
||||
from irctest.basecontrollers import BaseClientController
|
||||
from irctest.basecontrollers import NotImplementedByController
|
||||
|
||||
TEMPLATE_CONFIG = """
|
||||
[core]
|
||||
@ -45,8 +46,11 @@ class SopelController(BaseClientController):
|
||||
with self.open_file(self.filename) as fd:
|
||||
pass
|
||||
|
||||
def run(self, hostname, port, auth):
|
||||
def run(self, hostname, port, auth, tls_config):
|
||||
# Runs a client with the config given as arguments
|
||||
if tls_config is not None:
|
||||
raise NotImplementedByController(
|
||||
'TLS configuration')
|
||||
assert self.proc is None
|
||||
self.create_config()
|
||||
with self.open_file(self.filename) as fd:
|
||||
|
6
irctest/exceptions.py
Normal file
6
irctest/exceptions.py
Normal file
@ -0,0 +1,6 @@
|
||||
class NoMessageException(AssertionError):
|
||||
pass
|
||||
|
||||
class ConnectionClosed(Exception):
|
||||
pass
|
||||
|
@ -36,7 +36,7 @@ def parse_message(s):
|
||||
http://tools.ietf.org/html/rfc1459#section-2.3.1
|
||||
and
|
||||
http://ircv3.net/specs/core/message-tags-3.2.html"""
|
||||
assert s.endswith('\r\n'), 'Message does not end with CR LF'
|
||||
assert s.endswith('\r\n'), 'Message does not end with CR LF: {!r}'.format(s)
|
||||
s = s[0:-2]
|
||||
if s.startswith('@'):
|
||||
(tags, s) = s.split(' ', 1)
|
||||
|
4
irctest/tls.py
Normal file
4
irctest/tls.py
Normal file
@ -0,0 +1,4 @@
|
||||
import collections
|
||||
|
||||
TlsConfig = collections.namedtuple('TlsConfig',
|
||||
'enable trusted_fingerprints')
|
Reference in New Issue
Block a user