├── server.pl ├── https.service ├── openssl.cnf └── README.md /server.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(http/http_unix_daemon)). 2 | :- use_module(library(http/thread_httpd)). 3 | :- use_module(library(http/http_dispatch)). 4 | 5 | :- http_handler(/, handle_request, [prefix]). 6 | 7 | handle_request(_Request) :- 8 | format("Content-type: text/plain~n~n"), 9 | format("Hello!"). 10 | -------------------------------------------------------------------------------- /https.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=HTTPS Server 3 | 4 | [Service] 5 | UMask=022 6 | Environment=LANG=en_US.utf8 7 | Restart=on-failure 8 | ExecReload=/bin/kill -s HUP $MAINPID 9 | StartLimitInterval=60 10 | LimitNOFILE=1000 11 | StartLimitBurst=5 12 | WorkingDirectory=/home/you/letswicrypt 13 | ExecStart=/usr/local/bin/swipl server.pl --https --no-fork --user=www-data \ 14 | --certfile=/var/www/xyz.com/server.crt \ 15 | --keyfile=/var/www/xyz.com/server.key \ 16 | --workers=16 \ 17 | --cipherlist=EECDH+AESGCM:EDH+AESGCM:EECDH+AES256:EDH+AES256:CHACHA20 \ 18 | --keep_alive_timeout=2 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /openssl.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | # Policies used by the TSA examples. 31 | tsa_policy1 = 1.2.3.4.1 32 | tsa_policy2 = 1.2.3.4.5.6 33 | tsa_policy3 = 1.2.3.4.5.7 34 | 35 | #################################################################### 36 | [ ca ] 37 | default_ca = CA_default # The default ca section 38 | 39 | #################################################################### 40 | [ CA_default ] 41 | 42 | dir = ./demoCA # Where everything is kept 43 | certs = $dir/certs # Where the issued certs are kept 44 | crl_dir = $dir/crl # Where the issued crl are kept 45 | database = $dir/index.txt # database index file. 46 | #unique_subject = no # Set to 'no' to allow creation of 47 | # several ctificates with same subject. 48 | new_certs_dir = $dir/newcerts # default place for new certs. 49 | 50 | certificate = $dir/cacert.pem # The CA certificate 51 | serial = $dir/serial # The current serial number 52 | crlnumber = $dir/crlnumber # the current crl number 53 | # must be commented out to leave a V1 CRL 54 | crl = $dir/crl.pem # The current CRL 55 | private_key = $dir/private/cakey.pem# The private key 56 | RANDFILE = $dir/private/.rand # private random number file 57 | 58 | x509_extensions = usr_cert # The extentions to add to the cert 59 | 60 | # Comment out the following two lines for the "traditional" 61 | # (and highly broken) format. 62 | name_opt = ca_default # Subject Name options 63 | cert_opt = ca_default # Certificate field options 64 | 65 | # Extension copying option: use with caution. 66 | # copy_extensions = copy 67 | 68 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 69 | # so this is commented out by default to leave a V1 CRL. 70 | # crlnumber must also be commented out to leave a V1 CRL. 71 | # crl_extensions = crl_ext 72 | 73 | default_days = 365 # how long to certify for 74 | default_crl_days= 30 # how long before next CRL 75 | default_md = default # use public key default MD 76 | preserve = no # keep passed DN ordering 77 | 78 | # A few difference way of specifying how similar the request should look 79 | # For type CA, the listed attributes must be the same, and the optional 80 | # and supplied fields are just that :-) 81 | policy = policy_match 82 | 83 | # For the CA policy 84 | [ policy_match ] 85 | countryName = match 86 | stateOrProvinceName = match 87 | organizationName = match 88 | organizationalUnitName = optional 89 | commonName = supplied 90 | emailAddress = optional 91 | 92 | # For the 'anything' policy 93 | # At this point in time, you must list all acceptable 'object' 94 | # types. 95 | [ policy_anything ] 96 | countryName = optional 97 | stateOrProvinceName = optional 98 | localityName = optional 99 | organizationName = optional 100 | organizationalUnitName = optional 101 | commonName = supplied 102 | emailAddress = optional 103 | 104 | #################################################################### 105 | [ req ] 106 | default_bits = 2048 107 | default_keyfile = privkey.pem 108 | distinguished_name = req_distinguished_name 109 | attributes = req_attributes 110 | x509_extensions = v3_ca # The extentions to add to the self signed cert 111 | 112 | # Passwords for private keys if not present they will be prompted for 113 | # input_password = secret 114 | # output_password = secret 115 | 116 | # This sets a mask for permitted string types. There are several options. 117 | # default: PrintableString, T61String, BMPString. 118 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004) 119 | # utf8only: only UTF8Strings (PKIX recommendation after 2004). 120 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 121 | # MASK:XXXX a literal mask value. 122 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. 123 | string_mask = utf8only 124 | 125 | req_extensions = v3_req # The extensions to add to a certificate request 126 | 127 | [ req_distinguished_name ] 128 | countryName = Country Name (2 letter code) 129 | countryName_min = 2 130 | countryName_max = 2 131 | 132 | stateOrProvinceName = State or Province Name (full name) 133 | 134 | localityName = Locality Name (eg, city) 135 | 136 | 0.organizationName = Organization Name (eg, company) 137 | 138 | # we can do this but it is not needed normally :-) 139 | #1.organizationName = Second Organization Name (eg, company) 140 | #1.organizationName_default = World Wide Web Pty Ltd 141 | 142 | organizationalUnitName = Organizational Unit Name (eg, section) 143 | #organizationalUnitName_default = 144 | 145 | commonName = Common Name (e.g. server FQDN or YOUR name) 146 | commonName_default = www.xyz.com 147 | commonName_max = 64 148 | 149 | emailAddress = Email Address 150 | emailAddress_max = 64 151 | emailAddress_default = admin@xyz.com 152 | 153 | # SET-ex3 = SET extension number 3 154 | 155 | [ req_attributes ] 156 | challengePassword = A challenge password 157 | challengePassword_min = 4 158 | challengePassword_max = 20 159 | 160 | unstructuredName = An optional company name 161 | 162 | [ usr_cert ] 163 | 164 | # These extensions are added when 'ca' signs a request. 165 | 166 | # This goes against PKIX guidelines but some CAs do it and some software 167 | # requires this to avoid interpreting an end user certificate as a CA. 168 | 169 | basicConstraints=CA:FALSE 170 | 171 | # Here are some examples of the usage of nsCertType. If it is omitted 172 | # the certificate can be used for anything *except* object signing. 173 | 174 | # This is OK for an SSL server. 175 | # nsCertType = server 176 | 177 | # For an object signing certificate this would be used. 178 | # nsCertType = objsign 179 | 180 | # For normal client use this is typical 181 | # nsCertType = client, email 182 | 183 | # and for everything including object signing: 184 | # nsCertType = client, email, objsign 185 | 186 | # This is typical in keyUsage for a client certificate. 187 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 188 | 189 | # This will be displayed in Netscape's comment listbox. 190 | nsComment = "OpenSSL Generated Certificate" 191 | 192 | # PKIX recommendations harmless if included in all certificates. 193 | subjectKeyIdentifier=hash 194 | authorityKeyIdentifier=keyid,issuer 195 | 196 | # This stuff is for subjectAltName and issuerAltname. 197 | # Import the email address. 198 | # subjectAltName=email:copy 199 | # An alternative to produce certificates that aren't 200 | # deprecated according to PKIX. 201 | # subjectAltName=email:move 202 | 203 | # Copy subject details 204 | # issuerAltName=issuer:copy 205 | 206 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 207 | #nsBaseUrl 208 | #nsRevocationUrl 209 | #nsRenewalUrl 210 | #nsCaPolicyUrl 211 | #nsSslServerName 212 | 213 | # This is required for TSA certificates. 214 | # extendedKeyUsage = critical,timeStamping 215 | 216 | [ v3_req ] 217 | 218 | # Extensions to add to a certificate request 219 | 220 | basicConstraints = CA:FALSE 221 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 222 | subjectAltName = @alt_names 223 | 224 | [ alt_names ] 225 | DNS.1 = www.xyz.com 226 | DNS.2 = xyz.com 227 | 228 | [ v3_ca ] 229 | 230 | 231 | # Extensions for a typical CA 232 | 233 | 234 | # PKIX recommendation. 235 | 236 | subjectKeyIdentifier=hash 237 | 238 | authorityKeyIdentifier=keyid:always,issuer 239 | 240 | # This is what PKIX recommends but some broken software chokes on critical 241 | # extensions. 242 | #basicConstraints = critical,CA:true 243 | # So we do this instead. 244 | basicConstraints = CA:true 245 | 246 | # Key usage: this is typical for a CA certificate. However since it will 247 | # prevent it being used as an test self-signed certificate it is best 248 | # left out by default. 249 | # keyUsage = cRLSign, keyCertSign 250 | 251 | # Some might want this also 252 | # nsCertType = sslCA, emailCA 253 | 254 | # Include email address in subject alt name: another PKIX recommendation 255 | # subjectAltName=email:copy 256 | # Copy issuer details 257 | # issuerAltName=issuer:copy 258 | 259 | # DER hex encoding of an extension: beware experts only! 260 | # obj=DER:02:03 261 | # Where 'obj' is a standard or added object 262 | # You can even override a supported extension: 263 | # basicConstraints= critical, DER:30:03:01:01:FF 264 | 265 | [ crl_ext ] 266 | 267 | # CRL extensions. 268 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 269 | 270 | # issuerAltName=issuer:copy 271 | authorityKeyIdentifier=keyid:always 272 | 273 | [ proxy_cert_ext ] 274 | # These extensions should be added when creating a proxy certificate 275 | 276 | # This goes against PKIX guidelines but some CAs do it and some software 277 | # requires this to avoid interpreting an end user certificate as a CA. 278 | 279 | basicConstraints=CA:FALSE 280 | 281 | # Here are some examples of the usage of nsCertType. If it is omitted 282 | # the certificate can be used for anything *except* object signing. 283 | 284 | # This is OK for an SSL server. 285 | # nsCertType = server 286 | 287 | # For an object signing certificate this would be used. 288 | # nsCertType = objsign 289 | 290 | # For normal client use this is typical 291 | # nsCertType = client, email 292 | 293 | # and for everything including object signing: 294 | # nsCertType = client, email, objsign 295 | 296 | # This is typical in keyUsage for a client certificate. 297 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 298 | 299 | # This will be displayed in Netscape's comment listbox. 300 | nsComment = "OpenSSL Generated Certificate" 301 | 302 | # PKIX recommendations harmless if included in all certificates. 303 | subjectKeyIdentifier=hash 304 | authorityKeyIdentifier=keyid,issuer 305 | 306 | # This stuff is for subjectAltName and issuerAltname. 307 | # Import the email address. 308 | # subjectAltName=email:copy 309 | # An alternative to produce certificates that aren't 310 | # deprecated according to PKIX. 311 | # subjectAltName=email:move 312 | 313 | # Copy subject details 314 | # issuerAltName=issuer:copy 315 | 316 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 317 | #nsBaseUrl 318 | #nsRevocationUrl 319 | #nsRenewalUrl 320 | #nsCaPolicyUrl 321 | #nsSslServerName 322 | 323 | # This really needs to be in place for it to be a proxy certificate. 324 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 325 | 326 | #################################################################### 327 | [ tsa ] 328 | 329 | default_tsa = tsa_config1 # the default TSA section 330 | 331 | [ tsa_config1 ] 332 | 333 | # These are used by the TSA reply generation only. 334 | dir = ./demoCA # TSA root directory 335 | serial = $dir/tsaserial # The current serial number (mandatory) 336 | crypto_device = builtin # OpenSSL engine to use for signing 337 | signer_cert = $dir/tsacert.pem # The TSA signing certificate 338 | # (optional) 339 | certs = $dir/cacert.pem # Certificate chain to include in reply 340 | # (optional) 341 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional) 342 | 343 | default_policy = tsa_policy1 # Policy if request did not specify it 344 | # (optional) 345 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) 346 | digests = md5, sha1 # Acceptable message digests (mandatory) 347 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional) 348 | clock_precision_digits = 0 # number of digits after dot. (optional) 349 | ordering = yes # Is ordering defined for timestamps? 350 | # (optional, default: no) 351 | tsa_name = yes # Must the TSA name be included in the reply? 352 | # (optional, default: no) 353 | ess_cert_id_chain = no # Must the ESS cert id chain be included? 354 | # (optional, default: no) 355 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LetSWICrypt — Prolog HTTPS servers 2 | 3 | Prolog is extremely well suited for writing 4 | [**web applications**](https://www.metalevel.at/prolog/web). 5 | 6 | This repository explains how to set up and run secure (**HTTPS**) 7 | web servers using SWI-Prolog with *Let's Encrypt* and other 8 | certificate authorities. 9 | 10 | **Project page**: 11 | 12 | [**https://www.metalevel.at/letswicrypt/**](https://www.metalevel.at/letswicrypt/) 13 | 14 | # Requirements 15 | 16 | SWI-Prolog 7.5.8 or later ships with everything that is 17 | necessary to run HTTPS servers as described in the following. 18 | 19 | # Obtaining a certificate 20 | 21 | For the sake of concreteness, assume that we want to set up an 22 | HTTPS server that is reachable at `xyz.com` and 23 | `www.xyz.com`. These names are chosen also because they are easy to 24 | search for and do not occur anywhere else in the configuration files. 25 | 26 | ## Variant A: Use *Let's Encrypt* 27 | 28 | [**Let's Encrypt**](https://letsencrypt.org/) is a free certificate 29 | authority (CA). 30 | 31 | The tool is easy to install and run. Follow the instructions on their 32 | page, and then execute the following command on the host machine: 33 | 34 | $ sudo certbot certonly --standalone -d xyz.com -d www.xyz.com 35 | 36 | **Note**: This requires that you *stop* any server that listens on 37 | port 80 or port 443, until the certificate is obtained. There 38 | are also other ways to obtain a certificate that allow you to keep 39 | existing servers running. See below for more information. 40 | 41 | After this is completed, you obtain 4 files in `/etc/letsencrypt/live/xyz.com/`: 42 | 43 | /etc/letsencrypt/live/xyz.com/cert.pem 44 | /etc/letsencrypt/live/xyz.com/chain.pem 45 | /etc/letsencrypt/live/xyz.com/fullchain.pem 46 | /etc/letsencrypt/live/xyz.com/privkey.pem 47 | 48 | We only need two of them: 49 | 50 | - `privkey.pem`: the server's private key 51 | - `fullchain.pem`: the certificate and certificate chain. 52 | 53 | 54 | ## Variant B: Use a different certificate authority 55 | 56 | You can also use a different CA. To do that, you first create a new 57 | private key and certificate signing request (CSR). The file 58 | [openssl.cnf](openssl.cnf) shows you what is necessary to create 59 | a CSR for both `xyz.com` and `www.xyz.com`. The 60 | `alt_names` section is relevant to cover both domains: 61 | 62 | [ alt_names ] 63 | DNS.1 = www.xyz.com 64 | DNS.2 = xyz.com 65 | 66 | Using `openssl.cnf`, you can create the key (`server.key`) 67 | and CSR (`server.csr`) for example with: 68 | 69 | $ openssl req -out server.csr -new -newkey rsa:2048 -nodes -keyout server.key -config openssl.cnf 70 | 71 | You can inspect the created CSR with: 72 | 73 | $ openssl req -text -noout -verify -in server.csr 74 | 75 | To obtain a certificate, you have again two options: Either use a 76 | trusted CA (simply supply `server.csr`), or self-sign the 77 | key using for example: 78 | 79 | $ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -extensions v3_req -extfile openssl.cnf 80 | 81 | In both cases, the files that are important for the following are: 82 | 83 | - `server.key`: the server's private key 84 | - `server.crt`: the certificate and certificate chain. 85 | 86 | Note that—up to naming—this corresponds to the files 87 | obtained in Variant A. 88 | 89 | # Running an HTTPS server with SWI-Prolog 90 | 91 | In the previous section, we have seen two ways to obtain a private key 92 | and a certificate. For clarity, we have used different file names to 93 | distinguish the variants. We now assume the following files are 94 | available in `/var/www/xyz.com/`, no matter which variant you used to 95 | obtain them: 96 | 97 | - `server.key`: the server's private key 98 | - `server.crt`: the certificate and certificate chain. 99 | 100 | **Note**: You can store the certificate and key in any location, 101 | and also *leave* the files in `/etc/letsencrypt/live/` if you used 102 | *Let's Encrypt* to obtain them. This is because SWI-Prolog reads 103 | these files *before* dropping privileges when starting an 104 | HTTPS server. 105 | 106 | As the name suggests, the private key is meant to be kept 107 | *private*. Therefore, make sure to use suitable file permissions. 108 | 109 | You can inspect the issued certificate with: 110 | 111 | $ openssl x509 -in server.crt -text -noout 112 | 113 | ## Preliminaries: SWI-Prolog web server as Unix daemon 114 | 115 | The file [server.pl](server.pl) contains a very simple web server that 116 | is written using SWI-Prolog. In its current form, it simply replies 117 | with `Hello!` to any request. In a more realistic scenario, you 118 | would of course supply a more suitable definition 119 | of `handle_request/1`, so that the server replies with more 120 | useful content. Still, this basic server suffices to illustrate the 121 | principle for running an HTTPS server with any of the 122 | certificates we obtained in the previous steps. 123 | 124 | First, note that this server uses the [`http_unix_daemon` 125 | library](http://eu.swi-prolog.org/pldoc/doc/_SWI_/library/http/http_unix_daemon.pl). 126 | This library makes it extremely easy to run the web server as a 127 | Unix daemon by implicitly augmenting the code to let you 128 | configure the server using command line options. If you have an 129 | existing web server that you want to turn into a Unix daemon, 130 | simply add the following directive at the beginning: 131 | 132 |
133 | :- use_module(library(http/http_unix_daemon)).
134 | 
135 | 136 | Once you have done this, you can run the server with: 137 | 138 | $ swipl server.pl --port=PORT 139 | 140 | and it will automatically launch as a daemon process. During 141 | development, is is easier to work with the server on the terminal 142 | using an interactive Prolog toplevel, which you can enable with: 143 | 144 |
145 | $ swipl server.pl --port=PORT --interactive
146 | 
147 | 148 | where `PORT` is any free port on your system. Try for 149 | example `--port=3041`. 150 | 151 | To find out more available command line options, use: 152 | 153 | $ swipl server.pl --help 154 | 155 | ## Starting a Prolog HTTPS server 156 | 157 | To start an HTTPS server with SWI-Prolog, the following 3 command line 158 | options of the Unix daemon library are of particular relevance: 159 | 160 | - `--https`: enables HTTPS, using port 443 by default. 161 | - `--keyfile=FILE`: `FILE` contains the server's private key. 162 | - `--certfile=FILE`: `FILE` contains the certificate and certificate chain. 163 | 164 | So, in our case, we can launch the HTTPS server for example with: 165 | 166 | $ sudo swipl server.pl --https --user=you --keyfile=/var/www/xyz.com/server.key --certfile=/var/www/xyz.com/server.crt 167 | 168 | Note that running the server on port 443 requires root privileges. The 169 | `--user` option is necessary to drop privileges to the specified 170 | user after forking. 171 | 172 | ## Launching the HTTPS server on system startup 173 | 174 | To launch the HTTPS server on system startup, have a look at the 175 | `systemd` sample service file [`https.service`](https.service). 176 | 177 | Adjust the file as necessary, copy it to `/etc/systemd/system` and enable it with 178 | 179 | $ sudo systemctl enable /etc/systemd/system/https.service 180 | 181 | then start the service with: 182 | 183 | $ sudo systemctl start https.service 184 | 185 | # Making your server more secure 186 | 187 | Once your server is running, use for example 188 | [SSL Labs](https://www.ssllabs.com/) to assess the quality of its 189 | encryption settings. 190 | 191 | As of 2017, it is possible to obtain an **A+** rating with SWI-Prolog 192 | HTTPS servers, by using: 193 | 194 | - as ciphers (see command line option `--cipherlist`): `EECDH+AESGCM:EDH+AESGCM:EECDH+AES256:EDH+AES256:EECDH+CHACHA20:EDH+CHACHA20` 195 | - the `Strict-Transport-Security` header field, to enable HSTS. 196 | 197 | For additional security, you can encrypt the server's private key, 198 | using for example: 199 | 200 | $ openssl rsa -des -in server.key -out server.enc 201 | 202 | To use an encrypted key when starting the server, use the 203 | `--pwfile=FILE` command line option of the HTTP Unix daemon, 204 | where `FILE` stores the password and has suitably restrictive access 205 | permissions. 206 | 207 | # Renewing the certificate 208 | 209 | Once you have a web server running, you can use *Let's Encrypt* to 210 | obtain and *renew* your certificate *without stopping* the server. 211 | 212 | To use this feature, you must configure your web server to serve any 213 | files located in the directory **`.well-known`**. With the 214 | SWI-Prolog HTTP infrastructure, you can do this by adding the 215 | following directives to your server: 216 | 217 | :- use_module(library(http/http_files)). 218 | :- http_handler(root('.well-known/'), http_reply_from_files('.well-known', []), [prefix]). 219 | 220 | Restart the server and use the `--webroot` option as in the following 221 | example: 222 | 223 |
224 | $ sudo certbot certonly --webroot -w /var/www/xyz.com -d xyz.com -d www.xyz.com
225 | 
226 | 227 | Please see `man certbot` for further options. For example, using 228 | `--logs-dir`, `--config-dir` and `--work-dir`, you can configure paths 229 | so that you can run `certbot` *without* root privileges. In the 230 | example above, it is assumed that your web content is located in the 231 | directory `/var/www/xyz.com`. 232 | 233 | In this mode of operation, *Let's Encrypt* uses the existing web 234 | server and file contents to verify that you control the domain. 235 | 236 | After you have done this, you can renew the certificate any time with: 237 | 238 | $ certbot renew 239 | 240 | This automatically renews certificates that will expire within 241 | 30 days, again using the existing web server to establish you as 242 | the owner of the domain. You can run this command as a cronjob. 243 | 244 | After your certificate is renewed, you must restart your web server 245 | for the change to take effect. Alternatively, you can exchange 246 | certificates while the server keeps running, which is described below. 247 | 248 | # Exchanging certificates 249 | 250 | SWI-Prolog makes it possible to *exchange* certificates while 251 | the server *keeps running*. 252 | 253 | One way to do this is as follows: 254 | 255 | 1. Start your server *without* specifying a certificate or key. 256 | 2. Use the extensible predicate `http:ssl_server_create_hook/3` to add 257 | a certificate and key upon launch, while storing the original 258 | SSL context. See `ssl_add_certificate_key/4`. 259 | 3. When necessary, renew the certificate as explained above. Use 260 | `ssl_add_certificate_key/4` to add the new certificate to the 261 | original SSL context, obtaining a new context that is 262 | associated with the updated certificate. 263 | 4. Use the extensible predicate `http:ssl_server_open_client_hook/3` 264 | to use the new context when negotiating client connections. 265 | 266 | See the [SSL documentation](http://eu.swi-prolog.org/pldoc/doc_for?object=section(%27packages/ssl.html%27)) 267 | for more information. 268 | 269 | Using the original context as a baseline ensures that all command 270 | line options are adhered to and copied to new contexts that are 271 | created. For example, any specified *password* is securely retained in 272 | contexts and can therefore be used also for newly created keys. 273 | 274 | Note how [**logical purity**](https://www.metalevel.at/prolog/purity) 275 | of these predicates allows the thread-safe implementation of a feature 276 | that is not available in most other web servers. 277 | 278 | # Server Name Indication (SNI) 279 | 280 | To host multiple domains from a single IP address, you need **Server 281 | Name Indication** (SNI). This TLS extension lets you 282 | indicate different certificates and keys depending on the 283 | *host name* that the client accesses. 284 | 285 | The HTTP Unix daemon can be configured to use SNI by providing 286 | suitable clauses of the predicate `http:sni_options/2`. The first 287 | argument is the *host name*, and the second argument is a list of 288 | SSL options for that domain. The most important options are: 289 | 290 | - `certificate_file(+File)`: file that contains the **certificate** 291 | and certificate chain 292 | - `key_file(+File)`: file that contains the **private key**. 293 | 294 | For example, to specify a certificate and key for `abc.com` 295 | and `www.abc.com`, we can use: 296 | 297 |
298 | http:sni_options('abc.com', [certificate_file(CertFile),key_file(KeyFile)]) :-
299 |         CertFile = '/var/www/abc.com/server.crt',
300 |         KeyFile = '/var/www/abc.com/server.key'.
301 | http:sni_options('www.abc.com', Options) :-
302 |         http:sni_options('abc.com', Options).
303 | 
304 | 305 | # Doing it all manually 306 | 307 | Instead of relying on the Unix daemon library, you can also _manually_ 308 | start an HTTPS server via `http_server/2`. This gives you total 309 | control over all aspects of the server, including those that cannot be 310 | specified as command line options. The options for `ssl_context/3` are 311 | specified as `ssl(+Options)`. 312 | 313 | For example: 314 | 315 |
316 | :- use_module(library(http/thread_httpd)).
317 | :- use_module(library(http/http_ssl_plugin)).
318 | 
319 | https_server(Port, Options) :-
320 |         http_server(reply,
321 |                     [ port(Port),
322 |                       ssl([ certificate_file('/var/www/xyz.com/server.crt'),
323 |                             key_file('/var/www/xyz.com/server.key')
324 |                           ])
325 |                     | Options
326 |                     ]).
327 | reply(_) :-
328 |         format("Content-type: text/plain~n~n"),
329 |         format("Hello!").
330 | 
331 | 332 | Typical use cases do *not* require this. A better way to obtain the 333 | same effect is to rely on the HTTP Unix daemon library, and use the 334 | available hooks for more fine-grained control of SSL parameters. 335 | 336 | # Related topics 337 | 338 | Check out [**Proloxy**](https://github.com/triska/proloxy): It is a 339 | *reverse proxy* that is written entirely in SWI-Prolog. Use 340 | Proloxy if you want to provide access to different web services 341 | under a common umbrella URL. 342 | 343 | Importantly, you can run Proloxy as an HTTPS server and thus 344 | encrypt traffic of all hosted services at once. 345 | 346 | For more cryptographic functionality of SWI-Prolog, check out 347 | [**`library(crypto)`**](http://eu.swi-prolog.org/pldoc/man?section=crypto). This 348 | library provides predicates for reasoning about secure hashes, 349 | symmetric and asymmetric encryption, and **digital signatures**. 350 | 351 | See also the 352 | [**Cryptography**](https://www.metalevel.at/prolog/cryptography) 353 | chapter in 354 | [*The Power of Prolog*](https://www.metalevel.at/prolog). 355 | 356 | # Acknowledgments 357 | 358 | All this is is made possible thanks to: 359 | 360 | [**Jan Wielemaker**](http://eu.swi-prolog.org) for providing the 361 | Prolog system that made all this possible in the first place. 362 | 363 | [**Matt Lilley**](https://github.com/thetrime) for `library(ssl)`, the 364 | SSL wrapper library that ships with SWI-Prolog. The SWI-Prolog HTTPS 365 | server uses this library for secure connections. 366 | 367 | [**Charlie Hothersall-Thomas**](https://charlie.ht/) for 368 | implementation advice to enable more secure ciphers 369 | in `library(ssl)`. 370 | --------------------------------------------------------------------------------