LetSWICrypt - HTTPS Servers with Prolog
Introduction
Prolog is extremely well suited for writing
web applications.
This document explains how to set up and run secure
(HTTPS) web servers using SWI-Prolog with
Let's Encrypt and other certificate authorities.
You can download all files from a public git
repository: https://github.com/triska/letswicrypt
SWI-Prolog 7.5.8 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:
- privkey.pem: the server's private key
- fullchain.pem: the certificate and certificate chain.
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:
- server.key: the server's private key
- server.crt: the certificate and certificate chain.
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:
- server.key: the server's private key
- server.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
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:
:- 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
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:
- --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.
We can use openssl to try an encrypted connection to the server:
$ openssl s_client -connect localhost:443
Note that the s_client command is
intended exclusively for testing purposes!
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 2018, 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.
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:
$ 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.
Exchanging certificates
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. See ssl_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.
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:
- certificate_file(+File): file that contains the certificate
and certificate chain
- key_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).
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:
:- 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.
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.
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).
More about Prolog: The Power of Prolog
In particular: Web Applications
and Cryptography
Main page