Tutorial

In order for python-libtls to be usable, it will need to have access to libtls shared libraries. If these are installed into special “system” locations, then there is nothing more that needs doing. Otherwise, you will need to specify the location of the libtls binaries via an environment variable or other mechanism before you run any executable which uses python-libtls. Refer to the section on Specifying the location of libtls for more information.

If you are interested in TLS functionality for a server, see the Servers section. Otherwise, continue reading for information on using TLS as a client.

Clients

Connecting to a server

In general, you need to access a server which is not part of your organisation, so that its identity needs to be verified. This is generally true for any server which is accessed across the Internet.

Create a Context instance with no parameters:

>>> from tls import Context; context = Context()

Then connect it to the external server by specifying a host and port. We’ll specify that we want to connect to github.com on port 443, the default port for TLS traffic.

>>> tls_socket = context.connect('github.com', 443)

Verifying a server’s identity

At this point, a TCP connection will have been made to the server, but no verification of its identity has yet been made. To do so, we can invoke a TLS handshake explicitly.

>>> tls_socket.do_handshake()

At this point, either a secure connection to Github will have been made, or an exception will be raised. You can get some information about the connection, as well as what’s on the other end of the connection (the peer, in network parlance). The returned socket can now be used for secure communications.

>>> context.conn_version
'TLSv1.2'

This is the most secure version of TLS which is generally available.

>>> context.peer_cert_issuer
'/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 Extended Validation Server CA'

That’s the certificate authority (CA) which certifies that the server we’re connected to actually is a GitHub server.

You can also check certificate validity dates:

>>> context.peer_cert_notbefore
datetime.datetime(2016, 3, 10, 0, 0)
>>> context.peer_cert_notafter
datetime.datetime(2018, 5, 17, 12, 0)

You can send and receive data securely over the socket returned by the connect() call:

>>> tls_socket.sendall(b'GET / HTTP/1.1\r\nhost: github.com\r\n\r\n')
>>> data = tls_socket.recv(4096)
>>> len(data)
1370
>>> data[:68]
b'HTTP/1.1 200 OK\r\nServer: GitHub.com\r\nDate: Fri, 10 Feb 2017 16:53:39'

Asking for an OCSP response

Under certain circumstances, a certificate signed by a certifying authority may have been revoked after it was issued. In such cases, you can’t rely on the validity of the certificate itself, but need to cross-check against a database of revocations which is maintained by certificate authorities. OCSP (the Online Certificate Status Protocol) is used to obtain the revocation status of a certificate.

You can ask the server to which you are connecting to provide the revocation status of its certificate (which is obtained from an OCSP Responder and is tamper-proof). If it hasn’t been revoked, then you can rely on the certificate; if it has, then you can’t. The OCSP resoonse is said to be stapled to the responses sent during TLS negotiation with the server.

You can request the server to provide an OCSP response by specifying a require_ocsp_stapling= keyword argument when you create the context. If a response is provided, you can check OCSP information by looking at context attributes such as the peer_ocsp_result attribute.

If no OCSP response is provided, verification fails:

>>> from tls import Context; context = Context(require_ocsp_stapling=True)
>>> sock = context.connect('github.com', 443)
>>> sock.do_handshake()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tls/__init__.py", line 193, in do_handshake
    raise ValueError('handshake failed: %s' % context_error(context))
ValueError: handshake failed: no stapled OCSP response provided

OCSP stapling is not yet widely available, but where it is available, you can make use of it.

Disabling verification (INSECURE)

You may wish to disable verification if the server you are connecting to is used for development or test purposes, or is an internal server for which no certification is required. This is controlled by three keyword arguments which are specified when creating the context:

  • verify_cert - verifies the certificate signature against a certificate authority.
  • verify_name - verifies the server name against what it’s meant to be.
  • verify_time - verify that the certificate is valid at the time of checking.

These normally default to True, but you can specify False if you want to omit the relevant checks.

Specifying negotiation parameters

If you are familiar with the details of TLS, you can specify specific constraints which control how the TLS negotiation will work:

  • ciphers - a string of ciphers you want to use.
  • dhe_params - a string of parameters to use for the Diffie-Hellman key exchange.
  • ecdhe_curve - a string describing the parameters for the ECDHE (Elliptic Curve Diffie-Hellman Exchange) curve.
  • alpn - a string describing the parameters for the Application-Level Protocol Negotiation (ALPN). This is a comma separated list of protocols, in order of preference.

Servers

Preparing for client connections

Secure servers generally need to present proof of their identity to clients. Thus, for such servers, the minimum configuration will be something like

>>> context = Context(server=True, cafile='/path/to/ca_bundle.pem',
                      keypair_paths=('/path/to/server_cert.pem',
                                     '/path/to/server_key.pem'))

where the server_key.pem is the private key for the server which was created as part of the certification process, server_cert.pem is the signed certificate for the server, and ca_bundle.pem is the certificate bundle which certifies the server certificate.

Using a self-signed certificate

The configuration is the same as above, except that instead of a ca_bundle.pem which comes from a CA, you will provide a cacert.pem which you have created yourself.

Server Name Indication (SNI)

You don’t need to do anything special to deal with SNI - libtls deals with the details internally. When you create the server-side Context instance, pass all the certificate and key files you need to support in the extra_certificate_key_paths= keyword parameter, as a sequence of 2-tuples (certpath, keypath) or 3-tuples (certpath, keypath, ocsppath). See the documentation for server-side keyword parameters in the Context class for more information.

Asking clients to verify their identity

If you require the client to present a certificate as proof of its identity, you can specify verify_client=True along with the other parameters of the configuration.

Specifying an OCSP response

You can specify an OCSP response to be sent to the client by providing a 3-tuple for the keypair_paths= keyword argument when initialising the Context, which consists of the paths to the certificate file (PEM format), key file (PEM format) and OCSP response (DER format).

Supporting TLS sessions

If you need to support TLS sessions, you can use the session_id= and session_lifetime= parameters when setting up your context. See the documentation for server-side keyword parameters in the Context class for more information.

Asynchronous mode

While the above examples have shown sockets operating in synchronous mode, python-libtls can be used in asynchronous mode, too (i.e. with non-blocking sockets). One of the unit tests illustrates asynchronous operation using the asyncore Python standard library module. Preliminary tests have also shown successful operation with the Curio asynchronous library.

When using asynchronous mode, you typically create a socket and set it to be non-blocking, and then pass that socket into the connect() call on a Context instance. For example:

>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.setblocking(False)

Connecting is potentially a blocking operation. When connecting, or at any later point when you initiate a potentially blocking operation on this socket, such as a handshake or application data I/O, special exceptions may be raised to indicate that the call cannot proceed without blocking.

>>> tls_socket = context.connect(host, port, sock)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/vinay/projects/python-libtls/tls/__init__.py", line 542, in connect
    return self.wrap_socket(sock, server_hostname=hostname)
  File "/home/vinay/projects/python-libtls/tls/__init__.py", line 551, in wrap_socket
    self._ensure_connected(sock, hostname)
  File "/home/vinay/projects/python-libtls/tls/__init__.py", line 516, in _ensure_connected
    sock.connect((host, port))
BlockingIOError: [Errno 115] Operation now in progress

Under Python 2.x, the traceback is slightly different:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tls/__init__.py", line 542, in connect
    return self.wrap_socket(sock, server_hostname=hostname)
  File "tls/__init__.py", line 551, in wrap_socket
    self._ensure_connected(sock, hostname)
  File "tls/__init__.py", line 516, in _ensure_connected
    sock.connect((host, port))
  File "/usr/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 115] Operation now in progress

Normally, these exceptions will be handled by the asynchronous framework you are using. During application I/O, python-libtls generates the standard Python exceptions SSLWantReadError and SSLWantWriteError (defined in the built-in ssl module) to indicate that either data or buffer space is needed in order for the operation to complete. These exceptions are generally handled by the asynchronous framework you are using, in the same way as if you were using Python’s built-in ssl module.

Specifying the location of libtls

Linux, *BSD, OS X

This is generally done using the LD_LIBRARY_PATH environment variable, which must be the directory where the libraries are to be found.

Windows

You need to add the location of the libtls libraries to the Windows PATH environment variable, or have them in the current directory where the program that uses them resides.

Building your own libtls binaries

First, download the official source tarball for the relevant release from the official releases page. Don’t use any archives from GitHub - they are not suitable for building from. Unpack the archive into a folder, which will give you the sources in a folder named something like libressl-X.Y.Z. Navigate to this folder in a terminal window.

Linux, *BSD, OS X

It is assumed that you will have a C compiler on the system.

Invoke the following commands to configure, make and install libressl, which includes libtls:

$ ./configure --prefix=$HOME/opt
$ make
$ make install

The above assumes you don’t want to install it into a system location or don’t have root privileges.

After these commands have run, the libtls dynamic libraries will be in $HOME/opt/lib. You will need to specify this directory in the LD_LIBRARY_PATH environment variable before running a Python script which uses python-libtls.

Windows

It is assumed that you will have Visual Studio 15 or later on the system (earlier versions may not work).

You will also need a recent version of the CMake build tool. Issue commands in the root directory of your unpacked archive, using the following as a guide:

C> mkdir build
C> cd build
C> cmake -G"Visual Studio 14 2015 Win64" -DCMAKE_INSTALL_PREFIX=c:\Users\YourName\opt ..

The above example assumes that you want to build 64-bit versions of the libtls libraries, and that you want to install the software in c:\Users\YourName\opt. If you don’t specify the CMAKE_INSTALL_PREFIX, it defaults to C:\Program Files\LibreSSL.

Those commands create some build files in the current (build) directory. You can then build and install the libraries by invoking (for example):

C> cmake --build . --target INSTALL --config Release

which should build the libtls software and install it to c:\Users\YourName\opt, with the shared libraries being in c:\Users\YourName\opt\lib. You need to add this latter directory to your PATH, and python-libtls should then be usable.

Location of the default CA certificate bundle

libtls ships with a set of CA certificates, and they are to be found in a file called cert.pem which is located in directory etc/ssl relative to the installation directory (in the above examples, $HOME/opt on POSIX and c:\Users\YourName\opt on Windows). If you don’t specify a capath= or a cafile= argument when setting up your Context, libtls will look for a CA bundle in this default location. If not found, you will not be able to perform verification – python-libtls will throw exceptions if you haven’t disabled verification.

If you always specify a capath= or a cafile= argument referencing your own bundle, then this location isn’t searched.