{"id":21665904,"url":"https://github.com/triska/letswicrypt","last_synced_at":"2026-01-04T08:03:14.270Z","repository":{"id":83451294,"uuid":"58219843","full_name":"triska/letswicrypt","owner":"triska","description":"Prolog HTTPS Servers","archived":false,"fork":false,"pushed_at":"2018-02-11T20:33:23.000Z","size":27,"stargazers_count":35,"open_issues_count":1,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-03-20T03:10:32.337Z","etag":null,"topics":["https","https-server","prolog","swi-prolog"],"latest_commit_sha":null,"homepage":"https://www.metalevel.at/letswicrypt/","language":"Prolog","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/triska.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-05-06T16:19:23.000Z","updated_at":"2023-09-25T21:01:15.000Z","dependencies_parsed_at":"2023-10-15T16:55:49.734Z","dependency_job_id":null,"html_url":"https://github.com/triska/letswicrypt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/triska%2Fletswicrypt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/triska%2Fletswicrypt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/triska%2Fletswicrypt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/triska%2Fletswicrypt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/triska","download_url":"https://codeload.github.com/triska/letswicrypt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244563625,"owners_count":20472892,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["https","https-server","prolog","swi-prolog"],"created_at":"2024-11-25T11:18:35.191Z","updated_at":"2026-01-04T08:03:09.233Z","avatar_url":"https://github.com/triska.png","language":"Prolog","funding_links":[],"categories":["Server"],"sub_categories":[],"readme":"# LetSWICrypt \u0026mdash; Prolog HTTPS servers\n\nProlog is extremely well suited for writing\n[**web\u0026nbsp;applications**](https://www.metalevel.at/prolog/web).\n\nThis repository explains how to set up and run secure (**HTTPS**)\nweb\u0026nbsp;servers using SWI-Prolog with *Let's\u0026nbsp;Encrypt* and other\ncertificate authorities.\n\n**Project page**:\n\n[**https://www.metalevel.at/letswicrypt/**](https://www.metalevel.at/letswicrypt/)\n\n# Requirements\n\nSWI-Prolog \u003cb\u003e7.5.8\u003c/b\u003e or later ships with everything that is\nnecessary to run HTTPS\u0026nbsp;servers as described in the following.\n\n# Obtaining a certificate\n\nFor the sake of concreteness, assume that we want to set up an\nHTTPS\u0026nbsp;server that is reachable at `xyz.com` and\n`www.xyz.com`. These names are chosen also because they are easy to\nsearch for and do not occur anywhere else in the configuration files.\n\n## Variant A: Use *Let's Encrypt*\n\n[**Let's Encrypt**](https://letsencrypt.org/) is a free certificate\nauthority\u0026nbsp;(CA).\n\nThe tool is easy to install and run. Follow the instructions on their\npage, and then execute the following command on the host machine:\n\n    $ sudo certbot certonly --standalone -d xyz.com -d www.xyz.com\n\n**Note**: This requires that you *stop* any server that listens on\nport\u0026nbsp;80 or port\u0026nbsp;443, until the certificate is obtained. There\nare also other ways to obtain a certificate that allow you to keep\nexisting servers running. See below for more information.\n\nAfter this is completed, you obtain 4 files in `/etc/letsencrypt/live/xyz.com/`:\n\n    /etc/letsencrypt/live/xyz.com/cert.pem\n    /etc/letsencrypt/live/xyz.com/chain.pem\n    /etc/letsencrypt/live/xyz.com/fullchain.pem\n    /etc/letsencrypt/live/xyz.com/privkey.pem\n\nWe only need two of them:\n\n  - `privkey.pem`: the server's private key\n  - `fullchain.pem`: the certificate and certificate chain.\n\n\n## Variant B: Use a different certificate authority\n\nYou can also use a different CA. To do that, you first create a new\nprivate key and certificate signing request\u0026nbsp;(CSR). The file\n[openssl.cnf](openssl.cnf) shows you what is necessary to create\na\u0026nbsp;CSR for both `xyz.com` and\u0026nbsp;`www.xyz.com`. The\n`alt_names`\u0026nbsp;section is relevant to cover both domains:\n\n    [ alt_names ]\n    DNS.1 = www.xyz.com\n    DNS.2 = xyz.com\n\nUsing\u0026nbsp;`openssl.cnf`, you can create the key\u0026nbsp;(`server.key`)\nand CSR\u0026nbsp;(`server.csr`) for example with:\n\n    $ openssl req -out server.csr -new -newkey rsa:2048 -nodes -keyout server.key -config openssl.cnf\n\nYou can inspect the created CSR with:\n\n    $ openssl req -text -noout -verify -in server.csr\n\nTo obtain a certificate, you have again two options: Either use a\ntrusted\u0026nbsp;CA (simply supply\u0026nbsp;`server.csr`), or self-sign the\nkey using for example:\n\n    $ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -extensions v3_req -extfile openssl.cnf\n\nIn both cases, the files that are important for the following are:\n\n  - `server.key`: the server's private key\n  - `server.crt`: the certificate and certificate chain.\n\nNote that\u0026mdash;up to naming\u0026mdash;this corresponds to the files\nobtained in Variant\u0026nbsp;A.\n\n# Running an HTTPS server with SWI-Prolog\n\nIn the previous section, we have seen two ways to obtain a private key\nand a certificate. For clarity, we have used different file names to\ndistinguish the variants. We now assume the following files are\navailable in `/var/www/xyz.com/`, no matter which variant you used to\nobtain them:\n\n  - `server.key`: the server's private key\n  - `server.crt`: the certificate and certificate chain.\n\n**Note**: You can store the certificate and\u0026nbsp;key in any location,\nand also *leave* the files in `/etc/letsencrypt/live/` if you used\n*Let's\u0026nbsp;Encrypt* to obtain them. This is because SWI-Prolog reads\nthese files *before* dropping privileges when starting an\nHTTPS\u0026nbsp;server.\n\nAs the name suggests, the private key is meant to be kept\n*private*. Therefore, make sure to use suitable file permissions.\n\nYou can inspect the issued certificate with:\n\n    $ openssl x509 -in server.crt -text -noout\n\n## Preliminaries: SWI-Prolog web server as Unix daemon\n\nThe file [server.pl](server.pl) contains a very simple web server that\nis written using SWI-Prolog. In its current form, it simply replies\nwith\u0026nbsp;`Hello!` to any request. In a more realistic scenario, you\nwould of course supply a more suitable definition\nof\u0026nbsp;`handle_request/1`, so that the server replies with more\nuseful content. Still, this basic server suffices to illustrate the\nprinciple for running an HTTPS\u0026nbsp;server with any of the\ncertificates we obtained in the previous steps.\n\nFirst, note that this server uses the [`http_unix_daemon`\nlibrary](http://eu.swi-prolog.org/pldoc/doc/_SWI_/library/http/http_unix_daemon.pl).\nThis library makes it extremely easy to run the web\u0026nbsp;server as a\nUnix\u0026nbsp;daemon by implicitly augmenting the code to let you\nconfigure the server using command line\u0026nbsp;options. If you have an\nexisting web server that you want to turn into a Unix daemon,\nsimply add the following directive at the beginning:\n\n\u003cpre\u003e\n:- use_module(library(http/http_unix_daemon)).\n\u003c/pre\u003e\n\nOnce you have done this, you can run the server with:\n\n    $ swipl server.pl --port=PORT\n\nand it will automatically launch as a daemon process. During\ndevelopment, is is easier to work with the server on the terminal\nusing an interactive Prolog\u0026nbsp;toplevel, which you can enable with:\n\n\u003cpre\u003e\n$ swipl server.pl --port=PORT \u003cb\u003e--interactive\u003c/b\u003e\n\u003c/pre\u003e\n\nwhere `PORT` is any free port on your system. Try for\nexample\u0026nbsp;`--port=3041`.\n\nTo find out more available command line options, use:\n\n    $ swipl server.pl --help\n\n## Starting a Prolog HTTPS server\n\nTo start an HTTPS server with SWI-Prolog, the following 3 command line\noptions of the Unix daemon library are of particular relevance:\n\n  - `--https`: enables HTTPS, using port 443 by default.\n  - `--keyfile=FILE`: `FILE` contains the server's private key.\n  - `--certfile=FILE`: `FILE` contains the certificate and certificate chain.\n\nSo, in our case, we can launch the HTTPS server for example with:\n\n    $ sudo swipl server.pl --https --user=you --keyfile=/var/www/xyz.com/server.key --certfile=/var/www/xyz.com/server.crt\n\nNote that running the server on port 443 requires root privileges. The\n`--user`\u0026nbsp;option is necessary to drop privileges to the specified\nuser after forking.\n\n## Launching the HTTPS server on system startup\n\nTo launch the HTTPS server on system startup, have a look at the\n`systemd` sample service file [`https.service`](https.service).\n\nAdjust the file as necessary, copy it to `/etc/systemd/system` and enable it with\n\n    $ sudo systemctl enable /etc/systemd/system/https.service\n\nthen start the service with:\n\n    $ sudo systemctl start https.service\n\n# Making your server more secure\n\nOnce your server is running, use for example\n[SSL\u0026nbsp;Labs](https://www.ssllabs.com/) to assess the quality of its\nencryption settings.\n\nAs of 2017, it is possible to obtain an **A+** rating with SWI-Prolog\nHTTPS servers, by using:\n\n  - as ciphers (see command line option `--cipherlist`): `EECDH+AESGCM:EDH+AESGCM:EECDH+AES256:EDH+AES256:EECDH+CHACHA20:EDH+CHACHA20`\n  - the `Strict-Transport-Security` header field, to enable HSTS.\n\nFor additional security, you can encrypt the server's private key,\nusing for example:\n\n    $ openssl rsa -des -in server.key -out server.enc\n\nTo use an encrypted key when starting the server, use the\n`--pwfile=FILE` command line\u0026nbsp;option of the HTTP Unix daemon,\nwhere `FILE` stores the password and has suitably restrictive access\npermissions.\n\n# Renewing the certificate\n\nOnce you have a web server running, you can use *Let's Encrypt* to\nobtain and *renew* your certificate *without stopping* the server.\n\nTo use this feature, you must configure your web server to serve any\nfiles located in the directory\u0026nbsp;**`.well-known`**. With the\nSWI-Prolog HTTP\u0026nbsp;infrastructure, you can do this by adding the\nfollowing directives to your\u0026nbsp;server:\n\n    :- use_module(library(http/http_files)).\n    :- http_handler(root('.well-known/'), http_reply_from_files('.well-known', []), [prefix]).\n\nRestart the server and use the `--webroot` option as in the following\nexample:\n\n\u003cpre\u003e\n$ sudo certbot certonly \u003cb\u003e--webroot\u003c/b\u003e -w /var/www/xyz.com -d xyz.com -d www.xyz.com\n\u003c/pre\u003e\n\nPlease see `man certbot` for further options. For example, using\n`--logs-dir`, `--config-dir` and `--work-dir`, you can configure paths\nso that you can run `certbot` *without* root\u0026nbsp;privileges. In the\nexample above, it is assumed that your web content is located in the\ndirectory\u0026nbsp;`/var/www/xyz.com`.\n\nIn this mode of operation, *Let's Encrypt* uses the existing web\nserver and file contents to verify that you control the domain.\n\nAfter you have done this, you can renew the certificate any time with:\n\n    $ certbot renew\n\nThis automatically renews certificates that will expire within\n30\u0026nbsp;days, again using the existing web server to establish you as\nthe owner of the\u0026nbsp;domain. You can run this command as a cronjob.\n\nAfter your certificate is renewed, you must restart your web server\nfor the change to take\u0026nbsp;effect. Alternatively, you can exchange\ncertificates while the server keeps running, which is described below.\n\n# Exchanging certificates\n\nSWI-Prolog makes it possible to *exchange* certificates while\nthe\u0026nbsp;server *keeps\u0026nbsp;running*.\n\nOne way to do this is as follows:\n\n1. Start your server *without* specifying a certificate or key.\n2. Use the extensible predicate `http:ssl_server_create_hook/3` to add\n   a certificate and key upon launch, while storing the original\n   SSL\u0026nbsp;context. See\u0026nbsp;`ssl_add_certificate_key/4`.\n3. When necessary, renew the certificate as explained above. Use\n   `ssl_add_certificate_key/4` to add the new certificate to the\n   original SSL\u0026nbsp;context, obtaining a new\u0026nbsp;context that is\n   associated with the updated certificate.\n4. Use the extensible predicate `http:ssl_server_open_client_hook/3`\n   to use the new\u0026nbsp;context when negotiating client connections.\n\nSee the [SSL documentation](http://eu.swi-prolog.org/pldoc/doc_for?object=section(%27packages/ssl.html%27))\nfor more information.\n\nUsing the original context as a baseline ensures that all command\nline\u0026nbsp;options are adhered to and copied to new contexts that are\ncreated. For example, any specified *password* is securely retained in\ncontexts and can therefore be used also for newly created\u0026nbsp;keys.\n\nNote how [**logical purity**](https://www.metalevel.at/prolog/purity)\nof these predicates allows the thread-safe implementation of a feature\nthat is not available in most other web\u0026nbsp;servers.\n\n# Server Name Indication (SNI)\n\nTo host multiple domains from a single IP address, you need **Server\nName Indication**\u0026nbsp;(SNI). This TLS\u0026nbsp;extension lets you\nindicate different certificates and keys depending on the\n*host\u0026nbsp;name* that the client accesses.\n\nThe HTTP Unix daemon can be configured to use\u0026nbsp;SNI by providing\nsuitable clauses of the predicate\u0026nbsp;`http:sni_options/2`. The first\nargument is the *host\u0026nbsp;name*, and the second argument is a list of\nSSL\u0026nbsp;options for that domain. The most important options\u0026nbsp;are:\n\n  - `certificate_file(+File)`: file that contains the **certificate**\n    and certificate\u0026nbsp;chain\n  - `key_file(+File)`: file that contains the **private key**.\n\nFor example, to specify a certificate and key for\u0026nbsp;`abc.com`\nand\u0026nbsp;`www.abc.com`, we can\u0026nbsp;use:\n\n\u003cpre\u003e\nhttp:sni_options('abc.com', [certificate_file(CertFile),key_file(KeyFile)]) :-\n        CertFile = '/var/www/abc.com/server.crt',\n        KeyFile = '/var/www/abc.com/server.key'.\nhttp:sni_options('www.abc.com', Options) :-\n        http:sni_options('abc.com', Options).\n\u003c/pre\u003e\n\n# Doing it all manually\n\nInstead of relying on the Unix daemon library, you can also _manually_\nstart an HTTPS\u0026nbsp;server via `http_server/2`. This gives you total\ncontrol over all aspects of the server, including those that cannot be\nspecified as command line options. The options for `ssl_context/3` are\nspecified as\u0026nbsp;`ssl(+Options)`.\n\nFor example:\n\n\u003cpre\u003e\n:- use_module(library(http/thread_httpd)).\n:- use_module(library(http/http_ssl_plugin)).\n\nhttps_server(Port, Options) :-\n        http_server(reply,\n                    [ port(Port),\n                      \u003cb\u003essl([ certificate_file('/var/www/xyz.com/server.crt'),\n                            key_file('/var/www/xyz.com/server.key')\n                          ])\u003c/b\u003e\n                    | Options\n                    ]).\nreply(_) :-\n        format(\"Content-type: text/plain~n~n\"),\n        format(\"Hello!\").\n\u003c/pre\u003e\n\nTypical use cases do *not* require this. A better way to obtain the\nsame effect is to rely on the HTTP Unix daemon library, and use the\navailable hooks for more fine-grained control of SSL\u0026nbsp;parameters.\n\n# Related topics\n\nCheck out [**Proloxy**](https://github.com/triska/proloxy): It is a\n*reverse\u0026nbsp;proxy* that is written entirely in SWI-Prolog. Use\nProloxy if you want to provide access to different web\u0026nbsp;services\nunder a common umbrella\u0026nbsp;URL.\n\nImportantly, you can run Proloxy as an HTTPS\u0026nbsp;server and thus\nencrypt traffic of all hosted services at once.\n\nFor more cryptographic functionality of SWI-Prolog, check out\n[**`library(crypto)`**](http://eu.swi-prolog.org/pldoc/man?section=crypto). This\nlibrary provides predicates for reasoning about secure hashes,\nsymmetric and asymmetric encryption, and **digital\u0026nbsp;signatures**.\n\nSee also the\n[**Cryptography**](https://www.metalevel.at/prolog/cryptography)\nchapter in\n[*The\u0026nbsp;Power\u0026nbsp;of\u0026nbsp;Prolog*](https://www.metalevel.at/prolog).\n\n# Acknowledgments\n\nAll this is is made possible thanks to:\n\n[**Jan Wielemaker**](http://eu.swi-prolog.org) for providing the\nProlog system that made all this possible in the first place.\n\n[**Matt Lilley**](https://github.com/thetrime) for `library(ssl)`, the\nSSL wrapper library that ships with SWI-Prolog. The SWI-Prolog HTTPS\nserver uses this library for secure connections.\n\n[**Charlie Hothersall-Thomas**](https://charlie.ht/) for\nimplementation advice to enable more secure ciphers\nin\u0026nbsp;`library(ssl)`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftriska%2Fletswicrypt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftriska%2Fletswicrypt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftriska%2Fletswicrypt/lists"}