TxSNI-0.2.0/0000755000076600000240000000000013642544754012743 5ustar glyphstaff00000000000000TxSNI-0.2.0/PKG-INFO0000644000076600000240000000303213642544754014036 0ustar glyphstaff00000000000000Metadata-Version: 1.1 Name: TxSNI Version: 0.2.0 Summary: easy-to-use SNI endpoint for twisted Home-page: https://github.com/glyph/txsni Author: UNKNOWN Author-email: UNKNOWN License: MIT Description: txsni ===== .. image:: https://travis-ci.org/glyph/txsni.svg?branch=master :target: https://travis-ci.org/glyph/txsni Simple support for running a TLS server with Twisted. Use it like this: .. code-block:: console $ mkdir certificates $ cat private-stuff/mydomain.key.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/mydomain.crt.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/my-certificate-authority-chain.crt.pem >> \ certificates/mydomain.example.com.pem $ twist web --port txsni:certificates:tcp:443 Enjoy! Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography TxSNI-0.2.0/twisted/0000755000076600000240000000000013642544754014426 5ustar glyphstaff00000000000000TxSNI-0.2.0/twisted/plugins/0000755000076600000240000000000013642544754016107 5ustar glyphstaff00000000000000TxSNI-0.2.0/twisted/plugins/txsni_endpoint.py0000644000076600000240000000011713111433640021503 0ustar glyphstaff00000000000000 from txsni.parser import SNIDirectoryParser dirParser = SNIDirectoryParser() TxSNI-0.2.0/setup.py0000644000076600000240000000224013642543137014445 0ustar glyphstaff00000000000000 import os from setuptools import setup base_dir = os.path.dirname(__file__) with open(os.path.join(base_dir, "README.rst")) as f: long_description = f.read() setup( name="TxSNI", description="easy-to-use SNI endpoint for twisted", packages=[ "txsni", "txsni.test", "txsni.test.certs", "twisted.plugins", ], install_requires=[ "Twisted[tls]>=14.0", "pyOpenSSL>=0.14", ], version="0.2.0", long_description=long_description, license="MIT", url="https://github.com/glyph/txsni", classifiers=[ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", ], ) TxSNI-0.2.0/txsni/0000755000076600000240000000000013642544754014110 5ustar glyphstaff00000000000000TxSNI-0.2.0/txsni/tlsendpoint.py0000644000076600000240000000055013111433640017003 0ustar glyphstaff00000000000000from twisted.protocols.tls import TLSMemoryBIOFactory class TLSEndpoint(object): def __init__(self, endpoint, contextFactory): self.endpoint = endpoint self.contextFactory = contextFactory def listen(self, factory): return self.endpoint.listen(TLSMemoryBIOFactory( self.contextFactory, False, factory )) TxSNI-0.2.0/txsni/test/0000755000076600000240000000000013642544754015067 5ustar glyphstaff00000000000000TxSNI-0.2.0/txsni/test/test_txsni.py0000644000076600000240000002633213462470200017633 0ustar glyphstaff00000000000000from __future__ import absolute_import from functools import partial from txsni.snimap import SNIMap, HostDirectoryMap from txsni.tlsendpoint import TLSEndpoint from txsni.only_noticed_pypi_pem_after_i_wrote_this import objectsFromPEM from txsni.parser import SNIDirectoryParser from OpenSSL.crypto import load_certificate, FILETYPE_PEM from OpenSSL.SSL import Context, SSLv23_METHOD, Connection from twisted.internet import protocol, endpoints, reactor, defer, interfaces from twisted.internet.ssl import ( CertificateOptions, optionsForClientTLS, Certificate ) from twisted.python.filepath import FilePath from twisted.trial import unittest from zope.interface import implementer from .certs.cert_builder import ( ROOT_CERT_PATH, HTTP2BIN_CERT_PATH, CERT_DIR, _build_certs, ) # We need some temporary certs. _build_certs() with open(ROOT_CERT_PATH, 'rb') as f: PEM_ROOT = Certificate.loadPEM(f.read()) def sni_endpoint(): """ Builds a TxSNI TLSEndpoint populated with the default certificates. These are built from cert_builder.py, and have the following certs in the SNI map: - DEFAULT.pem, which contains a SAN for 'localhost'. - http2bin.org.pem, which contains a SAN for 'http2bin.org' """ base_endpoint = endpoints.TCP4ServerEndpoint( reactor=reactor, port=0, interface='127.0.0.1', ) path = FilePath(CERT_DIR) mapping = SNIMap(HostDirectoryMap(path)) wrapper_endpoint = TLSEndpoint(base_endpoint, mapping) return wrapper_endpoint def handshake( client_factory, server_factory, hostname, server_endpoint, acceptable_protocols=None, ): """ Connect a basic Twisted TLS client endpoint to the provided TxSNI TLSEndpoint. Returns a Deferred that fires when the connection has been established with a tuple of an instance of the client protocol and the listening port. """ def connect_client(listening_port): port_number = listening_port.getHost().port client = endpoints.TCP4ClientEndpoint( reactor, '127.0.0.1', port_number ) maybe_alpn = {} if acceptable_protocols is not None: maybe_alpn['acceptableProtocols'] = acceptable_protocols options = optionsForClientTLS( hostname=hostname, trustRoot=PEM_ROOT, **maybe_alpn ) client = endpoints.wrapClientTLS(options, client) connectDeferred = client.connect(client_factory) def aggregate(client_proto): return (client_proto, listening_port) connectDeferred.addCallback(aggregate) return connectDeferred listenDeferred = server_endpoint.listen(server_factory) listenDeferred.addCallback(connect_client) return listenDeferred class WritingProtocol(protocol.Protocol): """ A really basic Twisted protocol that fires a Deferred when the TLS handshake has been completed. It detects this using dataReceived, because we can't rely on IHandshakeListener. """ def __init__(self, handshake_deferred): self.handshake_deferred = handshake_deferred def dataReceived(self, data): cert = self.transport.getPeerCertificate() proto = self.transport.negotiatedProtocol self.transport.abortConnection() self.handshake_deferred.callback((cert, proto)) self.handshake_deferred = None class WritingProtocolFactory(protocol.Factory): protocol = WritingProtocol def __init__(self, handshake_deferred): self.handshake_deferred = handshake_deferred def buildProtocol(self, addr): p = self.protocol(self.handshake_deferred) p.factory = self return p class WriteBackProtocol(protocol.Protocol): """ A really basic Twisted protocol that just writes some data to the connection. """ def connectionMade(self): self.transport.write(b'PING') self.transport.loseConnection() @implementer(interfaces.IProtocolNegotiationFactory) class NegotiatingFactory(protocol.Factory): """ A Twisted Protocol Factory that implements the protocol negotiation extensions """ def acceptableProtocols(self): return [b'h2', b'http/1.1'] class WritingNegotiatingFactory(WritingProtocolFactory, NegotiatingFactory): pass class TestSNIMap(unittest.TestCase): """ Tests of the basic SNIMap logic. """ def test_snimap_default(self): """ SNIMap preferentially loads the DEFAULT value from the mapping if it's present. """ options = CertificateOptions() mapping = {'DEFAULT': options} sni_map = SNIMap(mapping) conn = sni_map.serverConnectionForTLS(protocol.Protocol()) self.assertIs(conn.get_context()._obj, options.getContext()) def test_snimap_makes_its_own_defaults(self): """ If passed a mapping without a DEFAULT key, SNIMap will make its own default context. """ options = CertificateOptions() mapping = {'example.com': options} sni_map = SNIMap(mapping) conn = sni_map.serverConnectionForTLS(protocol.Protocol()) self.assertIsNot(conn.get_context(), options.getContext()) self.assertIsNotNone(conn.get_context()) def assert_cert_is(test_case, protocol_cert, cert_path): """ Assert that ``protocol_cert`` is the same certificate as the one at ``cert_path``. """ with open(cert_path, 'rb') as f: target_cert = load_certificate(FILETYPE_PEM, f.read()) test_case.assertEqual( protocol_cert.digest('sha256'), target_cert.digest('sha256') ) class TestCommunication(unittest.TestCase): """ Tests that use the full Twisted logic to validate that txsni works as expected. """ def test_specific_certificate(self): """ When a hostname TxSNI does know about, in this case 'http2bin.org', is provided, TxSNI returns the specific certificate. """ handshake_deferred = defer.Deferred() client_factory = WritingProtocolFactory(handshake_deferred) server_factory = protocol.Factory.forProtocol(WriteBackProtocol) endpoint = sni_endpoint() d = handshake( client_factory=client_factory, server_factory=server_factory, hostname=u'http2bin.org', server_endpoint=endpoint, ) def confirm_cert(args): cert, proto = args assert_cert_is(self, cert, HTTP2BIN_CERT_PATH) return d def close(args): client, port = args port.stopListening() handshake_deferred.addCallback(confirm_cert) handshake_deferred.addCallback(close) return handshake_deferred class TestPemObjects(unittest.TestCase, object): """ Tests for L{objectsFromPEM} """ def test_noObjects(self): """ The empty string returns an empty list of certificates. """ objects = objectsFromPEM(b"") self.assertEqual(objects.certificates, []) self.assertEqual(objects.keys, []) def will_use_tls_1_3(): """ Will OpenSSL negotiate TLS 1.3? """ ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) return connection.get_protocol_version_name() == u'TLSv1.3' class TestNegotiationStillWorks(unittest.TestCase): """ Tests that TxSNI doesn't break protocol negotiation. """ EXPECTED_PROTOCOL = b'h2' def assert_specific_cert_still_negotiates(self, perform_handshake): """ When TxSNI selects a specific cert, protocol negotiation still works. """ handshake_deferred = defer.Deferred() client_factory = WritingNegotiatingFactory(handshake_deferred) server_factory = NegotiatingFactory.forProtocol( WriteBackProtocol ) endpoint = sni_endpoint() d = perform_handshake( client_factory=client_factory, server_factory=server_factory, hostname=u'http2bin.org', server_endpoint=endpoint, ) def confirm_cert(args): cert, proto = args self.assertEqual(proto, self.EXPECTED_PROTOCOL) return d def close(args): client, port = args port.stopListening() handshake_deferred.addCallback(confirm_cert) handshake_deferred.addCallback(close) return handshake_deferred def test_specific_cert_still_negotiates_with_alpn(self): """ When TxSNI selects a specific cert, Application Level Protocol Negotiation (ALPN) still works. """ return self.assert_specific_cert_still_negotiates( partial(handshake, acceptable_protocols=[self.EXPECTED_PROTOCOL]) ) def test_specific_cert_still_negotiates_with_npn(self): """ When TxSNI selects a specific cert, Next Protocol Negotiation (NPN) still works. """ return self.assert_specific_cert_still_negotiates(handshake) if will_use_tls_1_3(): test_specific_cert_still_negotiates_with_npn.skip = ( "OpenSSL does not support NPN with TLS 1.3" ) class TestSNIDirectoryParser(unittest.TestCase): """ Tests the C{txsni} endpoint implementation. """ def setUp(self): self.directory_parser = SNIDirectoryParser() def test_recreated_certificates(self): """ L{SNIDirectoryParser} always uses the latest certificate for the requested domain. """ endpoint = self.directory_parser.parseStreamServer( reactor, CERT_DIR, 'tcp', port='0', interface='127.0.0.1') def handshake_and_check(_): handshake_deferred = defer.Deferred() client_factory = WritingProtocolFactory(handshake_deferred) server_factory = protocol.Factory.forProtocol(WriteBackProtocol) initiate_handshake_deferred = handshake( client_factory=client_factory, server_factory=server_factory, hostname=u"http2bin.org", server_endpoint=endpoint, ) def confirm_cert(args): cert, proto = args assert_cert_is(self, cert, HTTP2BIN_CERT_PATH) def close(args): client, port = args port.stopListening() exception = [None] def captureException(f): exception[0] = f def maybeRethrow(_): if exception[0] is not None: exception[0].raiseException() handshake_deferred.addCallback(confirm_cert) handshake_deferred.addErrback(captureException) handshake_deferred.addCallback(lambda _: initiate_handshake_deferred) handshake_deferred.addCallback(close) handshake_deferred.addCallback(maybeRethrow) return handshake_deferred def reset_http2bin_cert(_): FilePath(HTTP2BIN_CERT_PATH).remove() _build_certs() old_cert_handshake = handshake_and_check(None) old_cert_handshake.addCallback(reset_http2bin_cert) return old_cert_handshake.addCallback(handshake_and_check) TxSNI-0.2.0/txsni/test/__init__.py0000644000076600000240000000000013111433640017144 0ustar glyphstaff00000000000000TxSNI-0.2.0/txsni/test/certs/0000755000076600000240000000000013642544754016207 5ustar glyphstaff00000000000000TxSNI-0.2.0/txsni/test/certs/__init__.py0000644000076600000240000000000013111433640020264 0ustar glyphstaff00000000000000TxSNI-0.2.0/txsni/test/certs/cert_builder.py0000644000076600000240000001323413431140352021205 0ustar glyphstaff00000000000000from __future__ import absolute_import from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID from twisted.logger import Logger import datetime import uuid import os import tempfile ONE_DAY = datetime.timedelta(1, 0, 0) THIRTYISH_YEARS = datetime.timedelta(30 * 365, 0, 0) TENISH_YEARS = datetime.timedelta(10 * 365, 0, 0) # Various exportable constants that the tests can (and should!) use. CERT_DIR = tempfile.mkdtemp() ROOT_CERT_PATH = os.path.join(CERT_DIR, 'root_cert.pem') ROOT_KEY_PATH = os.path.join(CERT_DIR, 'root_cert.key') DEFAULT_CERT_PATH = os.path.join(CERT_DIR, 'DEFAULT.pem') DEFAULT_KEY_PATH = os.path.join(CERT_DIR, 'DEFAULT.key') HTTP2BIN_CERT_PATH = os.path.join(CERT_DIR, 'http2bin.org.pem') HTTP2BIN_KEY_PATH = os.path.join(CERT_DIR, 'http2bin.org.key') # A list of tuples that controls what certs get built and signed by the root. # Each tuple is (hostname, cert_path) # We'll probably never need the easy extensibility this provides, but hey, nvm! _CERTS = [ (u'localhost', DEFAULT_CERT_PATH), (u'http2bin.org', HTTP2BIN_CERT_PATH), ] _LOGGER = Logger() def _build_root_cert(): """ Builds a single root certificate that can be used to sign the others. This root cert is basically pretty legit, except for being totally bonkers. Returns a tuple of (certificate, key) for the CA, which can be used to build the leaves. """ if os.path.isfile(ROOT_CERT_PATH) and os.path.isfile(ROOT_KEY_PATH): _LOGGER.info("Root already exists, not regenerating.") with open(ROOT_CERT_PATH, 'rb') as f: certificate = x509.load_pem_x509_certificate( f.read(), default_backend() ) with open(ROOT_KEY_PATH, 'rb') as f: key = serialization.load_pem_private_key( f.read(), password=None, backend=default_backend() ) return certificate, key private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) public_key = private_key.public_key() builder = x509.CertificateBuilder() builder = builder.subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u'txsni signing service'), ])) builder = builder.issuer_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u'txsni signing service'), ])) builder = builder.not_valid_before(datetime.datetime.today() - ONE_DAY) builder = builder.not_valid_after( datetime.datetime.today() + THIRTYISH_YEARS ) builder = builder.serial_number(int(uuid.uuid4())) builder = builder.public_key(public_key) # Don't allow intermediates. builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=0), critical=True, ) certificate = builder.sign( private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend() ) # Write it out. with open(ROOT_KEY_PATH, 'wb') as f: f.write( private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) ) with open(ROOT_CERT_PATH, 'wb') as f: f.write( certificate.public_bytes(serialization.Encoding.PEM) ) _LOGGER.info("Built root certificate.") return certificate, private_key def _build_single_leaf(hostname, certfile, ca_cert, ca_key): """ Builds a single leaf certificate, signed by the CA's private key. """ if os.path.isfile(certfile): _LOGGER.info("{hostname} already exists, not regenerating", hostname=hostname) return private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) public_key = private_key.public_key() builder = x509.CertificateBuilder() builder = builder.subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, hostname), ])) builder = builder.issuer_name(ca_cert.subject) builder = builder.not_valid_before(datetime.datetime.today() - ONE_DAY) builder = builder.not_valid_after( datetime.datetime.today() + TENISH_YEARS ) builder = builder.serial_number(int(uuid.uuid4())) builder = builder.public_key(public_key) builder = builder.add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ) builder = builder.add_extension( x509.SubjectAlternativeName([ x509.DNSName(hostname) ]), critical=True, ) certificate = builder.sign( private_key=ca_key, algorithm=hashes.SHA256(), backend=default_backend() ) # Write it out. with open(certfile, 'wb') as f: f.write( private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) ) f.write( certificate.public_bytes(serialization.Encoding.PEM) ) _LOGGER.info("Built certificate for {hostname}", hostname=hostname) def _build_certs(): """ Builds all certificates. """ ca_cert, ca_key = _build_root_cert() for hostname, certfile in _CERTS: _build_single_leaf(hostname, certfile, ca_cert, ca_key) if __name__ == '__main__': _build_certs() TxSNI-0.2.0/txsni/__init__.py0000644000076600000240000000005213111433640016174 0ustar glyphstaff00000000000000 """ SNI support for Twisted servers. """ TxSNI-0.2.0/txsni/parser.py0000644000076600000240000000176513431140352015745 0ustar glyphstaff00000000000000 from os.path import expanduser from zope.interface import implementer from twisted.internet.interfaces import IStreamServerEndpointStringParser from twisted.internet.endpoints import serverFromString from twisted.plugin import IPlugin from txsni.snimap import SNIMap from txsni.snimap import HostDirectoryMap from twisted.python.filepath import FilePath from txsni.tlsendpoint import TLSEndpoint @implementer(IStreamServerEndpointStringParser, IPlugin) class SNIDirectoryParser(object): prefix = 'txsni' def parseStreamServer(self, reactor, pemdir, *args, **kw): def colonJoin(items): return ':'.join([item.replace(':', '\\:') for item in items]) sub = colonJoin(list(args) + ['='.join(item) for item in kw.items()]) subEndpoint = serverFromString(reactor, sub) contextFactory = SNIMap(HostDirectoryMap(FilePath(expanduser(pemdir)))) return TLSEndpoint(endpoint=subEndpoint, contextFactory=contextFactory) TxSNI-0.2.0/txsni/only_noticed_pypi_pem_after_i_wrote_this.py0000644000076600000240000000331313431140352024770 0ustar glyphstaff00000000000000 from OpenSSL.SSL import FILETYPE_PEM from twisted.internet.ssl import Certificate, KeyPair, CertificateOptions from collections import namedtuple PEMObjects = namedtuple('PEMObjects', ['certificates', 'keys']) def objectsFromPEM(pemdata): """ Load some objects from a PEM. """ certificates = [] keys = [] blobs = [b""] for line in pemdata.split(b"\n"): if line.startswith(b'-----BEGIN'): if b'CERTIFICATE' in line: blobs = certificates else: blobs = keys blobs.append(b'') blobs[-1] += line blobs[-1] += b'\n' keys = [KeyPair.load(key, FILETYPE_PEM) for key in keys] certificates = [Certificate.loadPEM(certificate) for certificate in certificates] return PEMObjects(keys=keys, certificates=certificates) def certificateOptionsFromPileOfPEM(pemdata): objects = objectsFromPEM(pemdata) if len(objects.keys) != 1: raise ValueError("Expected 1 private key, found %d" % tuple([len(objects.keys)])) privateKey = objects.keys[0] certificatesByFingerprint = dict( [(certificate.getPublicKey().keyHash(), certificate) for certificate in objects.certificates] ) if privateKey.keyHash() not in certificatesByFingerprint: raise ValueError("No certificate matching %s found") openSSLCert = certificatesByFingerprint.pop(privateKey.keyHash()).original openSSLKey = privateKey.original openSSLChain = [c.original for c in certificatesByFingerprint.values()] return CertificateOptions(certificate=openSSLCert, privateKey=openSSLKey, extraCertChain=openSSLChain) TxSNI-0.2.0/txsni/snimap.py0000644000076600000240000001326313462470200015736 0ustar glyphstaff00000000000000import collections from zope.interface import implementer from OpenSSL.SSL import Connection from twisted.internet.interfaces import IOpenSSLServerConnectionCreator from twisted.internet.ssl import CertificateOptions from txsni.only_noticed_pypi_pem_after_i_wrote_this import ( certificateOptionsFromPileOfPEM ) class _NegotiationData(object): """ A container for the negotiation data. """ __slots__ = [ 'npnAdvertiseCallback', 'npnSelectCallback', 'alpnSelectCallback', 'alpnProtocols' ] def __init__(self): self.npnAdvertiseCallback = None self.npnSelectCallback = None self.alpnSelectCallback = None self.alpnProtocols = None def negotiateNPN(self, context): if self.npnAdvertiseCallback is None or self.npnSelectCallback is None: return context.set_npn_advertise_callback(self.npnAdvertiseCallback) context.set_npn_select_callback(self.npnSelectCallback) def negotiateALPN(self, context): if self.alpnSelectCallback is None or self.alpnProtocols is None: return context.set_alpn_select_callback(self.alpnSelectCallback) context.set_alpn_protos(self.alpnProtocols) class _ConnectionProxy(object): """ A basic proxy for an OpenSSL Connection object that returns a ContextProxy wrapping the actual OpenSSL Context whenever it's asked for. """ def __init__(self, original, factory): self._obj = original self._factory = factory def get_context(self): """ A basic override of get_context to ensure that the appropriate proxy object is returned. """ ctx = self._obj.get_context() return _ContextProxy(ctx, self._factory) def __getattr__(self, attr): return getattr(self._obj, attr) def __setattr__(self, attr, val): if attr in ('_obj', '_factory'): self.__dict__[attr] = val else: setattr(self._obj, attr, val) def __delattr__(self, attr): return delattr(self._obj, attr) class _ContextProxy(object): """ A basic proxy object for the OpenSSL Context object that records the values of the NPN/ALPN callbacks, to ensure that they get set appropriately if a context is swapped out during connection setup. """ def __init__(self, original, factory): self._obj = original self._factory = factory def set_npn_advertise_callback(self, cb): self._factory._npnAdvertiseCallbackForContext(self._obj, cb) return self._obj.set_npn_advertise_callback(cb) def set_npn_select_callback(self, cb): self._factory._npnSelectCallbackForContext(self._obj, cb) return self._obj.set_npn_select_callback(cb) def set_alpn_select_callback(self, cb): self._factory._alpnSelectCallbackForContext(self._obj, cb) return self._obj.set_alpn_select_callback(cb) def set_alpn_protos(self, protocols): self._factory._alpnProtocolsForContext(self._obj, protocols) return self._obj.set_alpn_protos(protocols) def __getattr__(self, attr): return getattr(self._obj, attr) def __setattr__(self, attr, val): if attr in ('_obj', '_factory'): self.__dict__[attr] = val else: return setattr(self._obj, attr, val) def __delattr__(self, attr): return delattr(self._obj, attr) @implementer(IOpenSSLServerConnectionCreator) class SNIMap(object): def __init__(self, mapping): self.mapping = mapping self._negotiationDataForContext = collections.defaultdict( _NegotiationData ) try: self.context = self.mapping['DEFAULT'].getContext() except KeyError: self.context = CertificateOptions().getContext() self.context.set_tlsext_servername_callback( self.selectContext ) def selectContext(self, connection): oldContext = connection.get_context() newContext = self.mapping[connection.get_servername()].getContext() negotiationData = self._negotiationDataForContext[oldContext] negotiationData.negotiateNPN(newContext) negotiationData.negotiateALPN(newContext) connection.set_context(newContext) def serverConnectionForTLS(self, protocol): """ Construct an OpenSSL server connection. @param protocol: The protocol initiating a TLS connection. @type protocol: L{TLSMemoryBIOProtocol} @return: a connection @rtype: L{OpenSSL.SSL.Connection} """ conn = Connection(self.context, None) return _ConnectionProxy(conn, self) def _npnAdvertiseCallbackForContext(self, context, callback): self._negotiationDataForContext[context].npnAdvertiseCallback = ( callback ) def _npnSelectCallbackForContext(self, context, callback): self._negotiationDataForContext[context].npnSelectCallback = callback def _alpnSelectCallbackForContext(self, context, callback): self._negotiationDataForContext[context].alpnSelectCallback = callback def _alpnProtocolsForContext(self, context, protocols): self._negotiationDataForContext[context].alpnProtocols = protocols class HostDirectoryMap(object): def __init__(self, directoryPath): self.directoryPath = directoryPath def __getitem__(self, hostname): if hostname is None: hostname = "DEFAULT" filePath = self.directoryPath.child(hostname).siblingExtension(".pem") if filePath.isfile(): return certificateOptionsFromPileOfPEM(filePath.getContent()) else: raise KeyError("no pem file for " + hostname) TxSNI-0.2.0/setup.cfg0000644000076600000240000000007513642544754014566 0ustar glyphstaff00000000000000[wheel] universal = 1 [egg_info] tag_build = tag_date = 0 TxSNI-0.2.0/TxSNI.egg-info/0000755000076600000240000000000013642544754015402 5ustar glyphstaff00000000000000TxSNI-0.2.0/TxSNI.egg-info/PKG-INFO0000644000076600000240000000303213642544754016475 0ustar glyphstaff00000000000000Metadata-Version: 1.1 Name: TxSNI Version: 0.2.0 Summary: easy-to-use SNI endpoint for twisted Home-page: https://github.com/glyph/txsni Author: UNKNOWN Author-email: UNKNOWN License: MIT Description: txsni ===== .. image:: https://travis-ci.org/glyph/txsni.svg?branch=master :target: https://travis-ci.org/glyph/txsni Simple support for running a TLS server with Twisted. Use it like this: .. code-block:: console $ mkdir certificates $ cat private-stuff/mydomain.key.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/mydomain.crt.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/my-certificate-authority-chain.crt.pem >> \ certificates/mydomain.example.com.pem $ twist web --port txsni:certificates:tcp:443 Enjoy! Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography TxSNI-0.2.0/TxSNI.egg-info/SOURCES.txt0000644000076600000240000000066613642544754017276 0ustar glyphstaff00000000000000README.rst setup.cfg setup.py TxSNI.egg-info/PKG-INFO TxSNI.egg-info/SOURCES.txt TxSNI.egg-info/dependency_links.txt TxSNI.egg-info/requires.txt TxSNI.egg-info/top_level.txt twisted/plugins/txsni_endpoint.py txsni/__init__.py txsni/only_noticed_pypi_pem_after_i_wrote_this.py txsni/parser.py txsni/snimap.py txsni/tlsendpoint.py txsni/test/__init__.py txsni/test/test_txsni.py txsni/test/certs/__init__.py txsni/test/certs/cert_builder.pyTxSNI-0.2.0/TxSNI.egg-info/requires.txt0000644000076600000240000000004313642544754017777 0ustar glyphstaff00000000000000Twisted[tls]>=14.0 pyOpenSSL>=0.14 TxSNI-0.2.0/TxSNI.egg-info/top_level.txt0000644000076600000240000000001613642544754020131 0ustar glyphstaff00000000000000twisted txsni TxSNI-0.2.0/TxSNI.egg-info/dependency_links.txt0000644000076600000240000000000113642544754021450 0ustar glyphstaff00000000000000 TxSNI-0.2.0/README.rst0000644000076600000240000000110113431140352014401 0ustar glyphstaff00000000000000txsni ===== .. image:: https://travis-ci.org/glyph/txsni.svg?branch=master :target: https://travis-ci.org/glyph/txsni Simple support for running a TLS server with Twisted. Use it like this: .. code-block:: console $ mkdir certificates $ cat private-stuff/mydomain.key.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/mydomain.crt.pem >> certificates/mydomain.example.com.pem $ cat public-stuff/my-certificate-authority-chain.crt.pem >> \ certificates/mydomain.example.com.pem $ twist web --port txsni:certificates:tcp:443 Enjoy!