Skip to content

Commit

Permalink
tls: Add TLS support
Browse files Browse the repository at this point in the history
  • Loading branch information
Synss committed Oct 23, 2018
1 parent d11b2a9 commit 805d157
Show file tree
Hide file tree
Showing 9 changed files with 1,972 additions and 1 deletion.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
[next]

Add TLS support. Both client side and server side are implemented.
There is no support for session management or SNI callbacks.

API Changes

* random: `random` module renamed `_random`.
Expand Down
114 changes: 114 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,117 @@ the chain::

Note, however, that this verification is only one step in a private key
infrastructure and does not take CRLs, path length, etc. into account.


TLS client and server
---------------------

The `mbedtls.tls` module provides TLS clients and servers. The API
follows the recommendations of `PEP 543`_. Note, however, that the
Python standard SSL library does not follow the PEP so that this
library may not be a drop-in replacement.

Also, this library does not yet support SSL 3 and the parts of the
API that are not documented in relevant PEP are subject to change.

.. _PEP 543: https://www.python.org/dev/peps/pep-0543/

Here are some simple HTTP messages to pass from the client to the
server and back.

>>> get_request = "\r\n".join((
... "GET / HTTP/1.0",
... "",
... "")).encode("ascii")
...
>>> http_response = "\r\n".join((
... "HTTP/1.0 200 OK",
... "Content-Type: text/html",
... "",
... "<h2>Test Server</h2>",
... "<p>Successful connection.</p>",
... "")).encode("ascii")
...
>>> http_error = "\r\n".join((
... "HTTP/1.0 400 Bad Request",
... "",
... ""))
...

For this example, the trust store just consists in the root certificate
`ca0_crt` from the previous section.

>>> from mbedtls import tls
>>> trust_store = tls.TrustStore()
>>> trust_store.add(ca0_crt)

The next step is to configure the TLS contexts of the server and
of the client.

>>> srv_ctx = tls.ServerContext(tls.TLSConfiguration(
... trust_store=trust_store,
... certificate_chain=([ee0_crt, ca1_crt], ee0_key),
... validate_certificates=False,
... ))
...
>>> cli_ctx = tls.ClientContext(tls.TLSConfiguration(
... trust_store=trust_store,
... validate_certificates=True,
... ))
...

The contexts are then used to wrap TCP sockets.

>>> import socket
>>> srv = srv_ctx.wrap_socket(
... socket.socket(socket.AF_INET, socket.SOCK_STREAM))
...

>>> from contextlib import suppress
>>> def block(callback, *args, **kwargs):
... while True:
... with suppress(tls.WantReadError, tls.WantWriteError):
... return callback(*args, **kwargs)
...

>>> def server_main_loop(sock):
... conn, addr = sock.accept()
... block(conn.do_handshake)
... data = conn.recv(1024)
... if data == get_request:
... conn.sendall(http_response)
... else:
... conn.sendall(http_error)
...

The server starts in his own process to avoid blocking this example.

>>> import multiprocessing as mp
>>> srv.bind(("localhost", 8888))
>>> srv.listen(1)
>>> runner = mp.Process(target=server_main_loop, args=(srv, ))
>>> runner.start()

>>> cli = cli_ctx.wrap_socket(
... socket.socket(socket.AF_INET, socket.SOCK_STREAM),
... server_hostname=None,
... )
...
>>> cli.connect(("localhost", 8888))
>>> block(cli.do_handshake)
>>> cli.send(get_request)
18
>>> response = block(cli.recv, 1024)
>>> print(response.decode("ascii").replace("\r\n", "\n"))
HTTP/1.0 200 OK
Content-Type: text/html
<BLANKLINE>
<h2>Test Server</h2>
<p>Successful connection.</p>
<BLANKLINE>

The last step is to stop the extra process and close the sockets.

>>> cli.close()
>>> runner.join(1.0)
>>> srv.close()
3 changes: 2 additions & 1 deletion mbedtls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import mbedtls.hash as hash
import mbedtls.hmac as hmac
import mbedtls.pk as pk
import mbedtls.tls as tls


__all__ = ("cipher", "exceptions", "hash", "hmac", "pk")
__all__ = ("cipher", "exceptions", "hash", "hmac", "pk", "tls")
3 changes: 3 additions & 0 deletions mbedtls/_md.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def __get_supported_mds():
"""Return the set of digests supported by the generic
message digest module.
See Also:
mbedtls.tls.__get_supported_ciphersuites()
"""
md_lookup = {n: v for n, v in enumerate(MD_NAME)}
cdef const int* md_types = _md.mbedtls_md_list()
Expand Down
40 changes: 40 additions & 0 deletions mbedtls/_net.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Declarations from `mbedtls/net_sockets.h`."""

__author__ = "Mathias Laurin"
__copyright__ = "Copyright 2018, Mathias Laurin"
__license__ = "MIT License"


cdef:
enum: MBEDTLS_NET_PROTO_TCP = 0
enum: MBEDTLS_NET_PROTO_UDP = 1


cdef extern from "mbedtls/net_sockets.h":
ctypedef struct mbedtls_net_context:
int fd

void mbedtls_net_init(mbedtls_net_context *ctx)
void mbedtls_net_free(mbedtls_net_context *ctx)
int mbedtls_net_connect(
mbedtls_net_context *ctx,
const char *host, const char *port,
int proto)
int mbedtls_net_bind(
mbedtls_net_context *ctx,
const char *bind_ip,
const char *port,
int proto)
int mbedtls_net_accept(
mbedtls_net_context *bind_ctx,
mbedtls_net_context *client_ctx,
void *client_ip, size_t buf_size, size_t *ip_len)
int mbedtls_net_set_block(mbedtls_net_context *ctx)
int mbedtls_net_set_nonblock(mbedtls_net_context *ctx)
# mbedtls_net_usleep
int mbedtls_net_recv(void *ctx, unsigned char *buf, size_t len) nogil
int mbedtls_net_send(void *ctx, const unsigned char *buf, size_t len) nogil
int mbedtls_net_recv_timeout(
void *ctx,
unsigned char *buf, size_t len,
int timeout)

0 comments on commit 805d157

Please sign in to comment.