Home

Awesome

LetSWICrypt — Prolog HTTPS servers

Prolog is extremely well suited for writing web applications.

This repository explains how to set up and run secure (HTTPS) web servers using SWI-Prolog with Let's Encrypt and other certificate authorities.

Project page:

https://www.metalevel.at/letswicrypt/

Requirements

SWI-Prolog <b>7.5.8</b> or later ships with everything that is necessary to run HTTPS servers as described in the following.

Obtaining a certificate

For the sake of concreteness, assume that we want to set up an HTTPS server that is reachable at xyz.com and www.xyz.com. These names are chosen also because they are easy to search for and do not occur anywhere else in the configuration files.

Variant A: Use Let's Encrypt

Let's Encrypt is a free certificate authority (CA).

The tool is easy to install and run. Follow the instructions on their page, and then execute the following command on the host machine:

$ sudo certbot certonly --standalone -d xyz.com -d www.xyz.com

Note: This requires that you stop any server that listens on port 80 or port 443, until the certificate is obtained. There are also other ways to obtain a certificate that allow you to keep existing servers running. See below for more information.

After this is completed, you obtain 4 files in /etc/letsencrypt/live/xyz.com/:

/etc/letsencrypt/live/xyz.com/cert.pem
/etc/letsencrypt/live/xyz.com/chain.pem
/etc/letsencrypt/live/xyz.com/fullchain.pem
/etc/letsencrypt/live/xyz.com/privkey.pem

We only need two of them:

Variant B: Use a different certificate authority

You can also use a different CA. To do that, you first create a new private key and certificate signing request (CSR). The file openssl.cnf shows you what is necessary to create a CSR for both xyz.com and www.xyz.com. The alt_names section is relevant to cover both domains:

[ alt_names ]
DNS.1 = www.xyz.com
DNS.2 = xyz.com

Using openssl.cnf, you can create the key (server.key) and CSR (server.csr) for example with:

$ openssl req -out server.csr -new -newkey rsa:2048 -nodes -keyout server.key -config openssl.cnf

You can inspect the created CSR with:

$ openssl req -text -noout -verify -in server.csr

To obtain a certificate, you have again two options: Either use a trusted CA (simply supply server.csr), or self-sign the key using for example:

$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -extensions v3_req -extfile openssl.cnf

In both cases, the files that are important for the following are:

Note that—up to naming—this corresponds to the files obtained in Variant A.

Running an HTTPS server with SWI-Prolog

In the previous section, we have seen two ways to obtain a private key and a certificate. For clarity, we have used different file names to distinguish the variants. We now assume the following files are available in /var/www/xyz.com/, no matter which variant you used to obtain them:

Note: You can store the certificate and key in any location, and also leave the files in /etc/letsencrypt/live/ if you used Let's Encrypt to obtain them. This is because SWI-Prolog reads these files before dropping privileges when starting an HTTPS server.

As the name suggests, the private key is meant to be kept private. Therefore, make sure to use suitable file permissions.

You can inspect the issued certificate with:

$ openssl x509 -in server.crt -text -noout

Preliminaries: SWI-Prolog web server as Unix daemon

The file server.pl contains a very simple web server that is written using SWI-Prolog. In its current form, it simply replies with Hello! to any request. In a more realistic scenario, you would of course supply a more suitable definition of handle_request/1, so that the server replies with more useful content. Still, this basic server suffices to illustrate the principle for running an HTTPS server with any of the certificates we obtained in the previous steps.

First, note that this server uses the http_unix_daemon library. This library makes it extremely easy to run the web server as a Unix daemon by implicitly augmenting the code to let you configure the server using command line options. If you have an existing web server that you want to turn into a Unix daemon, simply add the following directive at the beginning:

<pre> :- use_module(library(http/http_unix_daemon)). </pre>

Once you have done this, you can run the server with:

$ swipl server.pl --port=PORT

and it will automatically launch as a daemon process. During development, is is easier to work with the server on the terminal using an interactive Prolog toplevel, which you can enable with:

<pre> $ swipl server.pl --port=PORT <b>--interactive</b> </pre>

where PORT is any free port on your system. Try for example --port=3041.

To find out more available command line options, use:

$ swipl server.pl --help

Starting a Prolog HTTPS server

To start an HTTPS server with SWI-Prolog, the following 3 command line options of the Unix daemon library are of particular relevance:

So, in our case, we can launch the HTTPS server for example with:

$ sudo swipl server.pl --https --user=you --keyfile=/var/www/xyz.com/server.key --certfile=/var/www/xyz.com/server.crt

Note that running the server on port 443 requires root privileges. The --user option is necessary to drop privileges to the specified user after forking.

Launching the HTTPS server on system startup

To launch the HTTPS server on system startup, have a look at the systemd sample service file https.service.

Adjust the file as necessary, copy it to /etc/systemd/system and enable it with

$ sudo systemctl enable /etc/systemd/system/https.service

then start the service with:

$ sudo systemctl start https.service

Making your server more secure

Once your server is running, use for example SSL Labs to assess the quality of its encryption settings.

As of 2017, it is possible to obtain an A+ rating with SWI-Prolog HTTPS servers, by using:

For additional security, you can encrypt the server's private key, using for example:

$ openssl rsa -des -in server.key -out server.enc

To use an encrypted key when starting the server, use the --pwfile=FILE command line option of the HTTP Unix daemon, where FILE stores the password and has suitably restrictive access permissions.

Renewing the certificate

Once you have a web server running, you can use Let's Encrypt to obtain and renew your certificate without stopping the server.

To use this feature, you must configure your web server to serve any files located in the directory .well-known. With the SWI-Prolog HTTP infrastructure, you can do this by adding the following directives to your server:

:- use_module(library(http/http_files)).
:- http_handler(root('.well-known/'), http_reply_from_files('.well-known', []), [prefix]).

Restart the server and use the --webroot option as in the following example:

<pre> $ sudo certbot certonly <b>--webroot</b> -w /var/www/xyz.com -d xyz.com -d www.xyz.com </pre>

Please see man certbot for further options. For example, using --logs-dir, --config-dir and --work-dir, you can configure paths so that you can run certbot without root privileges. In the example above, it is assumed that your web content is located in the directory /var/www/xyz.com.

In this mode of operation, Let's Encrypt uses the existing web server and file contents to verify that you control the domain.

After you have done this, you can renew the certificate any time with:

$ certbot renew

This automatically renews certificates that will expire within 30 days, again using the existing web server to establish you as the owner of the domain. You can run this command as a cronjob.

After your certificate is renewed, you must restart your web server for the change to take effect. Alternatively, you can exchange certificates while the server keeps running, which is described below.

Exchanging certificates

SWI-Prolog makes it possible to exchange certificates while the server keeps running.

One way to do this is as follows:

  1. Start your server without specifying a certificate or key.
  2. Use the extensible predicate http:ssl_server_create_hook/3 to add a certificate and key upon launch, while storing the original SSL context. See ssl_add_certificate_key/4.
  3. When necessary, renew the certificate as explained above. Use ssl_add_certificate_key/4 to add the new certificate to the original SSL context, obtaining a new context that is associated with the updated certificate.
  4. Use the extensible predicate http:ssl_server_open_client_hook/3 to use the new context when negotiating client connections.

See the SSL documentation for more information.

Using the original context as a baseline ensures that all command line options are adhered to and copied to new contexts that are created. For example, any specified password is securely retained in contexts and can therefore be used also for newly created keys.

Note how logical purity of these predicates allows the thread-safe implementation of a feature that is not available in most other web servers.

Server Name Indication (SNI)

To host multiple domains from a single IP address, you need Server Name Indication (SNI). This TLS extension lets you indicate different certificates and keys depending on the host name that the client accesses.

The HTTP Unix daemon can be configured to use SNI by providing suitable clauses of the predicate http:sni_options/2. The first argument is the host name, and the second argument is a list of SSL options for that domain. The most important options are:

For example, to specify a certificate and key for abc.com and www.abc.com, we can use:

<pre> http:sni_options('abc.com', [certificate_file(CertFile),key_file(KeyFile)]) :- CertFile = '/var/www/abc.com/server.crt', KeyFile = '/var/www/abc.com/server.key'. http:sni_options('www.abc.com', Options) :- http:sni_options('abc.com', Options). </pre>

Doing it all manually

Instead of relying on the Unix daemon library, you can also manually start an HTTPS server via http_server/2. This gives you total control over all aspects of the server, including those that cannot be specified as command line options. The options for ssl_context/3 are specified as ssl(+Options).

For example:

<pre> :- use_module(library(http/thread_httpd)). :- use_module(library(http/http_ssl_plugin)). https_server(Port, Options) :- http_server(reply, [ port(Port), <b>ssl([ certificate_file('/var/www/xyz.com/server.crt'), key_file('/var/www/xyz.com/server.key') ])</b> | Options ]). reply(_) :- format("Content-type: text/plain~n~n"), format("Hello!"). </pre>

Typical use cases do not require this. A better way to obtain the same effect is to rely on the HTTP Unix daemon library, and use the available hooks for more fine-grained control of SSL parameters.

Related topics

Check out Proloxy: It is a reverse proxy that is written entirely in SWI-Prolog. Use Proloxy if you want to provide access to different web services under a common umbrella URL.

Importantly, you can run Proloxy as an HTTPS server and thus encrypt traffic of all hosted services at once.

For more cryptographic functionality of SWI-Prolog, check out library(crypto). This library provides predicates for reasoning about secure hashes, symmetric and asymmetric encryption, and digital signatures.

See also the Cryptography chapter in The Power of Prolog.

Acknowledgments

All this is is made possible thanks to:

Jan Wielemaker for providing the Prolog system that made all this possible in the first place.

Matt Lilley for library(ssl), the SSL wrapper library that ships with SWI-Prolog. The SWI-Prolog HTTPS server uses this library for secure connections.

Charlie Hothersall-Thomas for implementation advice to enable more secure ciphers in library(ssl).