def test_readinto(self, server_cert, ca_cert, load_chain): """ A Server and Client context that successfully handshake can write bytes into buffers. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( certificate_chain=cert_chain, validate_certificates=False, ) client, server = assert_configs_work(self.BACKEND, client_config, server_config) # Firstly, test that if we send some data to the server, we can use # readinto to read it. We'll allocate a buffer that's too big and # check that it gets filled. message_size = len(HTTP_REQUEST) test_buffer = bytearray(message_size * 2) write_until_complete(client, server, HTTP_REQUEST) read_length = server.readinto(test_buffer, message_size * 2) assert read_length == message_size assert test_buffer[:message_size] == HTTP_REQUEST # Next, test that if we ask for more data than the buffer can hold we # just get the amount that fills the buffer. message_size = len(HTTP_RESPONSE) test_buffer = bytearray(message_size // 2) write_until_complete(server, client, HTTP_RESPONSE) read_length = client.readinto(test_buffer, message_size * 2) assert read_length == (message_size // 2) assert (test_buffer[:message_size // 2] == HTTP_RESPONSE[:message_size // 2])
def test_snicallback_fires_with_data(self, server_cert, ca_cert, load_chain, hostname): """ In a basic, successful TLS negotiation, the SNI callback will be fired and will provide the appropriate data. """ callback_args = [] def callback(*args): callback_args.append(args) return args[-1] cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration(certificate_chain=cert_chain, validate_certificates=False, sni_callback=callback) client, server = assert_configs_work(self.BACKEND, client_config, server_config, hostname=hostname) assert len(callback_args) == 1 conn_object, name, config = callback_args[0] assert conn_object is server assert config == server_config assert name == hostname
def test_no_validation(self, server_cert, load_chain): """ A Server and Client context that both have their validation settings disabled and otherwise use the default configuration can handshake. """ cert_chain = load_chain(self.BACKEND, server_cert) client_config = pep543.TLSConfiguration(validate_certificates=False) server_config = pep543.TLSConfiguration( validate_certificates=False, certificate_chain=cert_chain, ) assert_configs_work(self.BACKEND, client_config, server_config)
def test_server_validation(self, server_cert, ca_cert, load_chain): """ A Server and Client context, where the Client context is set to validate the server, and otherwise use the default configuration, can handshake. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( validate_certificates=False, certificate_chain=cert_chain, ) assert_configs_work(self.BACKEND, client_config, server_config)
def test_cipher_suite_not_in_enum(self, monkeypatch, context): """ When a buffer object returns a cipher that is not in the PEP543 CipherSuite enum object, it returns the cipher suite ID instead. """ # We try this with the cipher suite NULL-MD5. This may not work on all # OpenSSL versions, so if this test breaks investigate and maybe change # to a different one. EXTRA_CIPHER_ID = 0x0001 EXTRA_CIPHER_NAME = 'NULL-MD5' def md5_cipher(*args): return (EXTRA_CIPHER_NAME, None, None) monkeypatch.setattr('ssl.SSLObject.cipher', md5_cipher) cipher_list = pep543.DEFAULT_CIPHER_LIST + [EXTRA_CIPHER_ID] config = pep543.TLSConfiguration( trust_store=pep543.stdlib.STDLIB_BACKEND.trust_store.system(), ciphers=cipher_list) ctx = context(config) buffer = wrap_buffers(ctx) suite = buffer.cipher() assert not isinstance(suite, pep543.CipherSuite) assert suite == EXTRA_CIPHER_ID
def test_client_context_returns_configuration(self, ca_cert): """ A Client context initialized with a given configuration will return the configuration it was initialized with. """ trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) client_context = self.BACKEND.client_context(client_config) assert client_context.configuration is client_config
def test_mutual_validation(self, client_cert, server_cert, ca_cert, load_chain): """ A Server and Client context, where each context is configured to verify the other, can handshake. """ server_certchain = load_chain(self.BACKEND, server_cert) client_certchain = load_chain(self.BACKEND, client_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration( certificate_chain=client_certchain, trust_store=trust_store, ) server_config = pep543.TLSConfiguration( certificate_chain=server_certchain, trust_store=trust_store, ) assert_configs_work(self.BACKEND, client_config, server_config)
def test_bad_values_for_versions_client(self, lowest, highest, context): """ Using TLSConfiguration objects with a bad value for their minimum or maximum version raises a TLSError with Client contexts. """ config = pep543.TLSConfiguration(validate_certificates=False, lowest_supported_version=lowest, highest_supported_version=highest) ctx = context(config) with pytest.raises(pep543.TLSError): wrap_buffers(ctx)
def test_client_validation(self, client_cert, server_cert, ca_cert, load_chain): """ A Server and Client context, where the Server context is set to validate the client, and the client does not validate, and the client presents a cert chain, can handshake. """ server_certchain = load_chain(self.BACKEND, server_cert) client_certchain = load_chain(self.BACKEND, client_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration( validate_certificates=False, certificate_chain=client_certchain, ) server_config = pep543.TLSConfiguration( certificate_chain=server_certchain, trust_store=trust_store, ) assert_configs_work(self.BACKEND, client_config, server_config)
def test_can_detect_tls_protocol(self, server_cert, ca_cert, load_chain): """ A Server and Client context that successfully handshake will report the same TLS version. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( certificate_chain=cert_chain, validate_certificates=False, ) client, server = assert_configs_work(self.BACKEND, client_config, server_config) assert client.negotiated_tls_version() in pep543.TLSVersion assert server.negotiated_tls_version() in pep543.TLSVersion assert ( client.negotiated_tls_version() == server.negotiated_tls_version())
def test_can_detect_cipher(self, server_cert, ca_cert, load_chain): """ A Server and Client context that successfully handshake will report the same cipher. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( certificate_chain=cert_chain, validate_certificates=False, ) client, server = assert_configs_work(self.BACKEND, client_config, server_config) # The cipher should be either a CipherSuite or an int, and should match # the server. assert isinstance(client.cipher(), (pep543.CipherSuite, int)) assert isinstance(server.cipher(), (pep543.CipherSuite, int)) assert client.cipher() == server.cipher()
def test_no_tls_protocol_before_handhake(self, server_cert, ca_cert, load_chain): """ Before the TLS handshake is done, no TLS protocols is available. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( certificate_chain=cert_chain, validate_certificates=False, ) client_context = self.BACKEND.client_context(client_config) server_context = self.BACKEND.server_context(server_config) client_buffer = client_context.wrap_buffers(None) server_buffer = server_context.wrap_buffers() print(client_buffer.negotiated_tls_version()) assert client_buffer.negotiated_tls_version() is None assert server_buffer.negotiated_tls_version() is None
def assert_negotiated_protocol(self, context, negotiated_protocol): """ Test that the protocol negotiated is as expected. """ if negotiated_protocol is not None: negotiated_protocol = pep543.NextProtocol(negotiated_protocol) config = pep543.TLSConfiguration( validate_certificates=False, inner_protocols=(pep543.NextProtocol.H2, )) ctx = context(config) buffer = wrap_buffers(ctx) assert (buffer.negotiated_protocol() == negotiated_protocol)
def test_no_supported_cipher_suites(self, context): """ Using TLSConfiguration objects that have only unsupported cipher suites raises a TLSError. """ # We assume that no cipher suite will be defined with the code eeee. config = pep543.TLSConfiguration( ciphers=[0xeeee], trust_store=pep543.stdlib.STDLIB_BACKEND.trust_store.system()) ctx = context(config) with pytest.raises(pep543.TLSError) as e: wrap_buffers(ctx) assert "supported ciphers" in str(e)
def test_server_context_returns_configuration(self, server_cert, load_chain): """ A Server context initialized with a given configuration will return the configuration it was initialized with. """ cert_chain = load_chain(self.BACKEND, server_cert) server_config = pep543.TLSConfiguration( validate_certificates=False, certificate_chain=cert_chain, ) server_context = self.BACKEND.server_context(server_config) assert server_context.configuration is server_config
def test_snicallback_fails_with_exception(self, server_cert, ca_cert, load_chain): """ If the SNI callback raises an exception, the handshake fails. """ callback_args = [] def callback(*args): callback_args.append(args) raise ValueError("Whoops!") cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration(certificate_chain=cert_chain, validate_certificates=False, sni_callback=callback) # TODO: This is really overbroad, this error could come from anywhere. # We allow either the underlying error or TLSError here. with pytest.raises((pep543.TLSError, ValueError)): assert_configs_work(self.BACKEND, client_config, server_config) assert callback_args
def test_snicallback_fails_with_none(self, server_cert, ca_cert, load_chain, rval): """ If the SNI callback returns any non TLSConfiguration value, the handshake fails. """ callback_args = [] def callback(*args): callback_args.append(args) return rval cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration(certificate_chain=cert_chain, validate_certificates=False, sni_callback=callback) # TODO: This is really overbroad, this error could come from anywhere. with pytest.raises(pep543.TLSError): assert_configs_work(self.BACKEND, client_config, server_config) assert callback_args
def test_can_cleanly_shutdown(self, server_cert, ca_cert, load_chain): """ A Server and Client context that successfully handshake can succesfully perform a shutdown. """ cert_chain = load_chain(self.BACKEND, server_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration(trust_store=trust_store) server_config = pep543.TLSConfiguration( certificate_chain=cert_chain, validate_certificates=False, ) client, server = assert_configs_work(self.BACKEND, client_config, server_config) # We want to perform a shutdown now. loop_until_success(client, server, 'shutdown') # At this point, we should read EOF from both client and server. assert not client.read(8192) assert not server.read(8192) # We should also check that readinto returns EOF. reference_buffer = bytearray(8192) buffer = bytearray(8192) assert not client.readinto(buffer, 8192) assert buffer == reference_buffer assert not server.readinto(buffer, 8192) assert buffer == reference_buffer # And writes should raise errors. with pytest.raises(pep543.TLSError): client.write(b'will fail') with pytest.raises(pep543.TLSError): server.write(b'will fail')
def test_inner_protocol_overlap(self, client_cert, server_cert, ca_cert, inner_protocols, result, load_chain): """ A Server and Client context, when both contexts support the same inner protocols, either successfully negotiate an inner protocol or don't negotiate anything. """ server_certchain = load_chain(self.BACKEND, server_cert) client_certchain = load_chain(self.BACKEND, client_cert) trust_store = self.BACKEND.trust_store.from_pem_file(ca_cert['cert']) client_config = pep543.TLSConfiguration( certificate_chain=client_certchain, trust_store=trust_store, inner_protocols=inner_protocols) server_config = pep543.TLSConfiguration( certificate_chain=server_certchain, trust_store=trust_store, inner_protocols=inner_protocols) client, server = assert_configs_work(self.BACKEND, client_config, server_config) assert client.negotiated_protocol() in (result, None) assert server.negotiated_protocol() in (result, None)
def test_unknown_cipher_suites(self, monkeypatch, context): """ When a buffer object returns a cipher that doesn't appear to be suppported by the given OpenSSL implementation, a TLSError is raised. """ def unknown_cipher(*args): return ('not_a_tls_cipher_suite', None, None) monkeypatch.setattr('ssl.SSLObject.cipher', unknown_cipher) config = pep543.TLSConfiguration( trust_store=pep543.stdlib.STDLIB_BACKEND.trust_store.system()) ctx = context(config) buffer = wrap_buffers(ctx) with pytest.raises(pep543.TLSError): buffer.cipher()
def test_system_trust_store_loads(self, monkeypatch, context): """ When a context is instructed to load the system trust store, it calls load_default_certs. """ calls = 0 def load_default_certs(*args): nonlocal calls calls += 1 monkeypatch.setattr('ssl.SSLContext.load_default_certs', load_default_certs) config = pep543.TLSConfiguration( trust_store=pep543.stdlib.STDLIB_BACKEND.trust_store.system()) ctx = context(config) wrap_buffers(ctx) assert calls == 1