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/
SWI-Prolog 7.5.8 or later ships with everything that is necessary to run HTTPS servers as described in the following.
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.
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:
privkey.pem
: the server's private keyfullchain.pem
: the certificate and certificate chain.
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:
server.key
: the server's private keyserver.crt
: the certificate and certificate chain.
Note that—up to naming—this corresponds to the files obtained in Variant A.
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:
server.key
: the server's private keyserver.crt
: the certificate and certificate chain.
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
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:
:- use_module(library(http/http_unix_daemon)).
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:
$ swipl server.pl --port=PORT --interactive
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
To start an HTTPS server with SWI-Prolog, the following 3 command line options of the Unix daemon library are of particular relevance:
--https
: enables HTTPS, using port 443 by default.--keyfile=FILE
:FILE
contains the server's private key.--certfile=FILE
:FILE
contains the certificate and certificate chain.
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.
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
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:
- as ciphers (see command line option
--cipherlist
):EECDH+AESGCM:EDH+AESGCM:EECDH+AES256:EDH+AES256:EECDH+CHACHA20:EDH+CHACHA20
- the
Strict-Transport-Security
header field, to enable HSTS.
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.
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:
$ sudo certbot certonly --webroot -w /var/www/xyz.com -d xyz.com -d www.xyz.com
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.
SWI-Prolog makes it possible to exchange certificates while the server keeps running.
One way to do this is as follows:
- Start your server without specifying a certificate or key.
- Use the extensible predicate
http:ssl_server_create_hook/3
to add a certificate and key upon launch, while storing the original SSL context. Seessl_add_certificate_key/4
. - 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. - 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.
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:
certificate_file(+File)
: file that contains the certificate and certificate chainkey_file(+File)
: file that contains the private key.
For example, to specify a certificate and key for abc.com
and www.abc.com
, we can use:
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).
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:
:- use_module(library(http/thread_httpd)). :- use_module(library(http/http_ssl_plugin)). https_server(Port, Options) :- http_server(reply, [ port(Port), ssl([ certificate_file('/var/www/xyz.com/server.crt'), key_file('/var/www/xyz.com/server.key') ]) | Options ]). reply(_) :- format("Content-type: text/plain~n~n"), format("Hello!").
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.
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.
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)
.