From ebfbfc644ba8954b073bcb9a14c89aac5aa0e6dc Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Mon, 17 Dec 2018 16:42:40 +0200 Subject: [PATCH 1/8] First draft of HTTPS Guide --- guides/https.md | 201 ++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 12 ++- 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 guides/https.md diff --git a/guides/https.md b/guides/https.md new file mode 100644 index 00000000..f3127a51 --- /dev/null +++ b/guides/https.md @@ -0,0 +1,201 @@ +# HTTPS + +Plug can serve HTTP over TLS ('HTTPS') through an appropriately configured Adapter. While the exact syntax for defining an HTTPS listener is adapter-specific, Plug does define a common set of TLS configuration options that most adapters support, formally documented as `Plug.SSL.configure/1`. + +The current document describes how to use these parameters to set up an HTTPS server with Plug, and documents some best-practices and potential pitfalls. + +*Editor's note: The secure transport protocol used by HTTPS is nowadays referred to as TLS. However, the application in the Erlang/OTP standard library that implements it is called 'ssl', for historical reasons. In this document we will refer to the protocol as 'TLS' and to the Erlang/OTP implementation as 'ssl', and its configuration parameters as 'ssl options'.* + +## Prerequisites + +The prerequisites for running an HTTPS server with Plug include: + +* The Erlang/OTP runtime, built against OpenSSL; run `:crypto.info_lib()` in an IEx session to verify +* A Plug Adapter that supports HTTPS, e.g. [Plug.Cowboy](https://hex.pm/packages/plug_cowboy) +* A valid certificate and associated private key + +For testing purposes it may be sufficient to use a self-signed certificate. The Phoenix project includes a Mix task `phx.gen.cert` that will generate a key and self-signed certificate. Alternatively you could use the OpenSSL CLI or another utility to generate the necessary files. + +For staging and production you will want to obtain a CA-signed certificate from a Certificate Authority, such as [Let's Encrypt](https://letsencrypt.org). + +## Getting Started + +Typically the certificate and private key are provided as PEM-encoded files. Such files contain Base64 data between 'BEGIN' and 'END' markers. Certificates issued by a CA usually come with an additional file containing one or more certificates that make up the 'CA chain'. Some useful OpenSSL commands for converting certificates/keys from other formats to PEM format can be found at [the end of this document](#converting-certificates-and-keys). + +A minimal HTTPS listener, using Plug.Cowboy, might be defined as follows: + +```elixir +Plug.Cowboy.https MyPlug, [], + port: 8443, + cipher_suite: :strong, + certfile: "/etc/letsencrypt/live/example.net/cert.pem", + keyfile: "/etc/letsencrypt/live/example.net/privkey.pem", + cacertfile: "/etc/letsencrypt/live/example.net/chain.pem" +``` + +The `cacertfile` option is not needed when using a self-signed certificate. + +It is possible to bundle the certificate files with the application, possibly for packaging into a release. In this case the files must be stored under the application's 'priv' directory. The `otp_app` option must be set to the name of the OTP application that contains the files, in order to correctly resolve the relative paths: + +```elixir +Plug.Cowboy.https MyPlug, [], + port: 8443, + cipher_suite: :strong, + certfile: "priv/cert/selfsigned.pem", + keyfile: "priv/cert/selfsigned_key.pem", + otp_app: :my_app +``` + +## TLS Protocol Options + +In addition to a certificate, an HTTPS server needs a secure TLS protocol configuration. `Plug.SSL` always sets the following options: + +* Enable `secure_renegotiate`, to avoid certain types of man-in-the-middle attacks +* Enable `reuse_sessions`, for improved handshake performance of recurring connections + +Additional options can be set by selecting a predefined profile or by setting 'ssl' options individually. + +### Predefined Options + +To simplify configuration of TLS defaults Plug provides two preconfigured options: `cipher_suite: :strong` and `cipher_suite: :compatible`. + +The `:strong` profile enables AES-GCM ciphers with ECDHE or DHE key exchange, and TLS version 1.2 only. It is intended for typical installations with support for browsers and other modern clients. + +The `:compatible` profile additionally enables AES-CBC ciphers, as well as TLS versions 1.1 and 1.0. Use this configuration to allow connections from older clients, such as older PC or mobile operating systems. Note that RSA key exchange is not enabled by this configuration, due to known weaknesses, so to support clients that do not support ECDHE or DHE it is necessary specify the ciphers explicitly (see below). + +In addition, both profiles: + +* Configure the server to select a cipher suite from the client's offer based on its own preferences rather than the client's (`honor_cipher_order` set to `true`) +* Disable `client_renegotiation` +* Select the 'Prime' (SECP) curves for use in Elliptic Curve Cryptography (ECC) + +All these parameters, including the global defaults mentioned above, can be overridden by specifying custom 'ssl' configuration options. + +It is worth noting that the cipher lists and TLS protocol versions selected by the profiles are whitelists. If a new Erlang/OTP release introduces new TLS protocol versions or ciphers that are not included in the profile definition, they would have to be enabled explicitly by overriding the `ciphers` and/or `versions` options, until such time as they are added to the Plug.SSL profiles. + +The Ciphers chosen and related configuration are based on [OWASP recommendations](https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet), with some modifications as described in the `Plug.SSL.configure/1` documentation. + +### Manual configuration + +Please refer to the [Erlang/OTP 'ssl' documentation](http://erlang.org/doc/man/ssl.html) for details on the supported configuration options. + +An example configuration with custom 'ssl' options might look like this: + +```elixir +Plug.Cowboy.https MyPlug, [], + port: 8443, + certfile: "/etc/letsencrypt/live/example.net/cert.pem", + keyfile: "/etc/letsencrypt/live/example.net/privkey.pem", + cacertfile: "/etc/letsencrypt/live/example.net/chain.pem", + versions: [:"tlsv1.2", :"tlsv1.1"], + ciphers: [ + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-RSA-AES128-GCM-SHA256', + 'DHE-RSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256' + ], + honor_cipher_order: true, + sni_fun: &MyPlug.ssl_opts_for_hostname/1 +``` + +## HTTP Strict Transport Security (HSTS) + +Once a server is configured to support HTTPS it is often a good idea to redirect HTTP requests to HTTPS. To do this, include Plug.SSL in your Plug pipeline. + +To prevent downgrade attacks, in which an attacker tricks a victim into connecting over plain HTTP even if the server would normally redirect that user to HTTPS, Plug.SSL by default sets the 'Strict-Transport-Security' header to mark the domain as HTTPS-only in the browser. To disable this behaviour, pass `hsts: false` to Plug.SSL. + +## Encrypted Keys + +To protect the private key on disk it is best to store it in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `password` option: + +```elixir +Plug.Cowboy.https MyPlug, [], + port: 8443, + certfile: "/etc/letsencrypt/live/example.net/cert.pem", + keyfile: "/etc/letsencrypt/live/example.net/privkey_aes.pem", + cacertfile: "/etc/letsencrypt/live/example.net/chain.pem", + password: "SECRET" +``` + +To encrypt an existing PEM-encoded RSA key use the OpenSSL CLI: `openssl rsa -in privkey.pem -out privkey_aes.pem -aes128`. Use `ec` instead of `rsa` when using an ECDSA certificate. Don't forget to securely erase the unencrypted copy afterwards! Even better would be to encrypt the file during initial key generation: please refer to the instructions provided by your CA. + +It is important to note that, at the time of writing, Erlang/OTP does not support keys encrypted with AES-256. The OpenSSL command in the previous paragraph can also be used to convert an AES-256 encrypted key to AES-128. + +## Passing DER Binaries + +Sometimes it is preferable to not store the private key on disk at all. Instead, the private key might be passed to your application using an environment variable or retrieved from a key store such as Vault. + +In such cases it is possible to pass the private key directly, using the `key` parameter. For example, assuming an RSA private key is available in the PRIVKEY environment variable in Base64 encoded DER format, the key may be set as follows: + +```elixir +der = System.get_env("PRIVKEY") |> Base.decode64! +Plug.Cowboy.https MyPlug, [], + port: 8443, + key: {:RSAPrivateKey, der}, + #.... +``` + +Note that reading environment variables in Mix config files only works when starting the application using Mix, e.g. in a development environment. In production, a different approach is needed for runtime configuration, but this is out of scope for the current document. + +The certificate and CA chain can also be specified using DER binaries, using the `cert` and `cacerts` options, but this is best avoided. The use of PEM files has been tested much more thoroughly with the Erlang/OTP 'ssl' application, and there have been a number of issues with DER binary certificates in the past. + +## Custom Diffie-Hellman Parameters + +It is recommended to generate a custom set of Diffie Hellman parameters, to be used for the DHE key exchange. Use the following OpenSSL CLI command to create a 'dhparam.pem' file: + +`openssl dhparam -out dhparams.pem 4096` + +On a slow machine (e.g. a cheap VPS) this may take several hours. You may want to run the command on a strong machine and copy the file over to the target server: the file does not need to be kept secret. + +Pass the (relative or absolute) path using the `dhfile` option. It is best practice to rotate the file periodically. + +If no custom parameters are specified, Erlang's 'ssl' uses its built-in defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC 3526. + +## Renewing Certificates + +TODO + +## Listening on Port 443 + +By default clients expect HTTPS servers to listen on port 443. It is possible to specify a different port in HTTPS URLs, but for public servers it is often preferable to stick to the default. This presents a problem, however: only privileged processes can bind to TCP port numbers under 1024, and it is bad idea to run your Plug application as 'root'. + +Leaving aside solutions that rely on external network elements, such as load balancers, there are a few solutions on typical Linux servers: + +* Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see also 'Offloading TLS', below); the proxy will listen on port 443 and pass the traffic to the Elixir application running on an unprivileged port +* Create an IPTables rule to forward packets arriving on port 443 to the port on which the Elixir application is running +* Give the Erlang/OTP runtime (that is, the BEAM VM executable) permission to bind to privileged ports using 'setcap', e.g. `sudo setcap 'cap_net_bind_service=+ep' /usr/lib/erlang/erts-10.1/bin/beam.smp`; update the path as necessary, and remember to run the command again after Erlang upgrades +* Use a tools such as 'authbind' to give an unprivileged user/group permission to bind to specific ports + +This is not intended to be an exhaustive list, as this topic is actually a bit beyond the scope of the current document. The issue is a generic one, not specific to Erlang/Elixir, and a lot of documentation can be found online. + +## Offloading TLS + +So far this document has focussed on configuring Plug to handle TLS within the application. Some people instead prefer to terminate TLS in a proxy or load balancer deployed in front of the Plug application. + +TODO: +- Pros and cons +- X-Forwarded-For and X-Forwarded-Proto +- Using Plug.SSL +- 'Secure' cookies + +## Converting Certificates and Keys + +If your certificate and/or key files are not in PEM format, here are some OpenSSL commands you may use to convert the files for use with Plug. + +### From DER to PEM + +DER-encoded files contain binary data. Common file extensions are `.crt` for certificates and `.key` for keys. + +To convert a single DER-encoded certificate to PEM format: `openssl x509 -in server.crt -inform der -out cert.pem` + +To convert an RSA private key from DER to PEM format: `openssl rsa -in privkey.der -inform der -out privkey.pem`. You may want to add the `-aes128` argument to produce an encrypted, password protected PEM file. + +A DER private key may also be stored in a PKCS#8 container, which may be password protected. Such files sometimes have a `.p8` extension. In such cases use the following command to convert the private key to PEM format: `openssl pkcs8 -in privkey.p8 -inform der -out privkey.pem`. Again, add the `-aes128` argument if desired. + +### From PKCS#12 to PEM + +The PKCS#12 format is a container format potentially containing multiple certificates and/or encrypted keys. Such files typically have a `.p12` extension. + +To extract all certificates from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nokeys -out fullchain.pem`. The resulting file contains all certificates from the input file, typically the server certificate and any CA certificates that make up the CA chain. You can split the file into seperate `cert.pem` and `chain.pem` files using a text editor, or you can just pass `certfile: fullchain.pem` to the HTTPS adapter (Erlang's ssl will look for the CA chain in the certificate file if no separate `cacertfile` is specified). + +To extract an RSA private key from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nocerts -nodes -out privkey.pem`. You may want to replace the `-nodes` argument with `-aes128` to produce an encrypted, password protected PEM file. diff --git a/mix.exs b/mix.exs index aa326a51..d3416f43 100644 --- a/mix.exs +++ b/mix.exs @@ -16,9 +16,13 @@ defmodule Plug.MixProject do name: "Plug", xref: [exclude: @xref_exclude], docs: [ - extras: ["README.md"], + extras: [ + "README.md", + "guides/https.md" + ], main: "readme", groups_for_modules: groups_for_modules(), + groups_for_extras: groups_for_extras(), source_ref: "v#{@version}", source_url: "https://github.com/elixir-plug/plug" ] @@ -97,4 +101,10 @@ defmodule Plug.MixProject do ] ] end + + defp groups_for_extras do + [ + Guides: ~r/guides\/[^\/]+\.md/ + ] + end end From 6acaf6bdf77876f652e8b14d6938a48cab0e6ea3 Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Tue, 18 Dec 2018 16:54:19 +0200 Subject: [PATCH 2/8] Second draft of HTTPS Guide --- guides/https.md | 101 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/guides/https.md b/guides/https.md index f3127a51..632eb43c 100644 --- a/guides/https.md +++ b/guides/https.md @@ -2,7 +2,7 @@ Plug can serve HTTP over TLS ('HTTPS') through an appropriately configured Adapter. While the exact syntax for defining an HTTPS listener is adapter-specific, Plug does define a common set of TLS configuration options that most adapters support, formally documented as `Plug.SSL.configure/1`. -The current document describes how to use these parameters to set up an HTTPS server with Plug, and documents some best-practices and potential pitfalls. +This guide describes how to use these parameters to set up an HTTPS server with Plug, and documents some best-practices and potential pitfalls. *Editor's note: The secure transport protocol used by HTTPS is nowadays referred to as TLS. However, the application in the Erlang/OTP standard library that implements it is called 'ssl', for historical reasons. In this document we will refer to the protocol as 'TLS' and to the Erlang/OTP implementation as 'ssl', and its configuration parameters as 'ssl options'.* @@ -10,22 +10,32 @@ The current document describes how to use these parameters to set up an HTTPS se The prerequisites for running an HTTPS server with Plug include: -* The Erlang/OTP runtime, built against OpenSSL; run `:crypto.info_lib()` in an IEx session to verify +* The Erlang/OTP runtime, with OpenSSL bindings; run `:crypto.info_lib()` in an IEx session to verify * A Plug Adapter that supports HTTPS, e.g. [Plug.Cowboy](https://hex.pm/packages/plug_cowboy) * A valid certificate and associated private key -For testing purposes it may be sufficient to use a self-signed certificate. The Phoenix project includes a Mix task `phx.gen.cert` that will generate a key and self-signed certificate. Alternatively you could use the OpenSSL CLI or another utility to generate the necessary files. +### Self-signed Certificate -For staging and production you will want to obtain a CA-signed certificate from a Certificate Authority, such as [Let's Encrypt](https://letsencrypt.org). +For testing purposes it may be sufficient to use a self-signed certificate. Such certificates generally result in warnings in browsers and failed connections from other tools, but these can be overridden to enable HTTPS testing. This is especially useful for local testing of HTTP 2, which is only specified over TLS. -## Getting Started +**Warning**: *use self-signed certificates only for local testing, and do not mark such test certificates as globally trusted in browsers or operating system!* + +The [Phoenix](https://phoenixframework.org/) project includes a Mix task `phx.gen.cert` that generates the necessary files and places them in the application's 'priv' directory. The [X509](https://hex.pm/packages/x509) package can be used as a dev-only dependency to add a similar `x509.gen.selfsigned` Mix task to non-Phoenix projects. + +Alternatively, the OpenSSL CLI or other utilities can be used to generate a self-signed certificate. Instructions are widely available online. + +### CA Issued Certificate -Typically the certificate and private key are provided as PEM-encoded files. Such files contain Base64 data between 'BEGIN' and 'END' markers. Certificates issued by a CA usually come with an additional file containing one or more certificates that make up the 'CA chain'. Some useful OpenSSL commands for converting certificates/keys from other formats to PEM format can be found at [the end of this document](#converting-certificates-and-keys). +For staging and production it is necessary to obtain a CA-signed certificate from a trusted Certificate Authority, such as [Let's Encrypt](https://letsencrypt.org). Certificates issued by a CA usually come with an additional file containing one or more certificates that make up the 'CA chain'. + +For use with Plug the certificates and key should be stored in PEM format, containing Base64-encoded data between 'BEGIN' and 'END' markers. Some useful OpenSSL commands for converting certificates/keys from other formats can be found at [the end of this document](#converting-certificates-and-keys). + +## Getting Started A minimal HTTPS listener, using Plug.Cowboy, might be defined as follows: ```elixir -Plug.Cowboy.https MyPlug, [], +Plug.Cowboy.https MyApp.MyPlug, [], port: 8443, cipher_suite: :strong, certfile: "/etc/letsencrypt/live/example.net/cert.pem", @@ -33,12 +43,18 @@ Plug.Cowboy.https MyPlug, [], cacertfile: "/etc/letsencrypt/live/example.net/chain.pem" ``` -The `cacertfile` option is not needed when using a self-signed certificate. +The `cacertfile` option is not needed when using a self-signed certificate, or when the file pointed to by `certfile` contains both the server certificate and all necessary CA chain certificates: + +```elixir + #... + certfile: "/etc/letsencrypt/live/example.net/fullchain.pem", + keyfile: "/etc/letsencrypt/live/example.net/privkey.pem" +``` It is possible to bundle the certificate files with the application, possibly for packaging into a release. In this case the files must be stored under the application's 'priv' directory. The `otp_app` option must be set to the name of the OTP application that contains the files, in order to correctly resolve the relative paths: ```elixir -Plug.Cowboy.https MyPlug, [], +Plug.Cowboy.https MyApp.MyPlug, [], port: 8443, cipher_suite: :strong, certfile: "priv/cert/selfsigned.pem", @@ -46,6 +62,8 @@ Plug.Cowboy.https MyPlug, [], otp_app: :my_app ``` +Remember to exclude the files from version control, unless the certificate and key are shared by all developers for testing purposes only. For example, add this line to the '.gitignore' file: `priv/**/*.pem`. + ## TLS Protocol Options In addition to a certificate, an HTTPS server needs a secure TLS protocol configuration. `Plug.SSL` always sets the following options: @@ -61,11 +79,11 @@ To simplify configuration of TLS defaults Plug provides two preconfigured option The `:strong` profile enables AES-GCM ciphers with ECDHE or DHE key exchange, and TLS version 1.2 only. It is intended for typical installations with support for browsers and other modern clients. -The `:compatible` profile additionally enables AES-CBC ciphers, as well as TLS versions 1.1 and 1.0. Use this configuration to allow connections from older clients, such as older PC or mobile operating systems. Note that RSA key exchange is not enabled by this configuration, due to known weaknesses, so to support clients that do not support ECDHE or DHE it is necessary specify the ciphers explicitly (see below). +The `:compatible` profile additionally enables AES-CBC ciphers, as well as TLS versions 1.1 and 1.0. Use this configuration to allow connections from older clients, such as older PC or mobile operating systems. Note that RSA key exchange is not enabled by this configuration, due to known weaknesses, so to support clients that do not support ECDHE or DHE it is necessary specify the ciphers explicitly (see [below](#manual-configuration)). In addition, both profiles: -* Configure the server to select a cipher suite from the client's offer based on its own preferences rather than the client's (`honor_cipher_order` set to `true`) +* Configure the server to choose a cipher based on its own preferences rather than the client's (`honor_cipher_order` set to `true`); when specifying a custom cipher list, ensure the ciphers are listed in descending order of preference * Disable `client_renegotiation` * Select the 'Prime' (SECP) curves for use in Elliptic Curve Cryptography (ECC) @@ -73,16 +91,16 @@ All these parameters, including the global defaults mentioned above, can be over It is worth noting that the cipher lists and TLS protocol versions selected by the profiles are whitelists. If a new Erlang/OTP release introduces new TLS protocol versions or ciphers that are not included in the profile definition, they would have to be enabled explicitly by overriding the `ciphers` and/or `versions` options, until such time as they are added to the Plug.SSL profiles. -The Ciphers chosen and related configuration are based on [OWASP recommendations](https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet), with some modifications as described in the `Plug.SSL.configure/1` documentation. +The ciphers chosen and related configuration are based on [OWASP recommendations](https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet), with some modifications as described in the `Plug.SSL.configure/1` documentation. -### Manual configuration +### Manual Configuration Please refer to the [Erlang/OTP 'ssl' documentation](http://erlang.org/doc/man/ssl.html) for details on the supported configuration options. An example configuration with custom 'ssl' options might look like this: ```elixir -Plug.Cowboy.https MyPlug, [], +Plug.Cowboy.https MyApp.MyPlug, [], port: 8443, certfile: "/etc/letsencrypt/live/example.net/cert.pem", keyfile: "/etc/letsencrypt/live/example.net/privkey.pem", @@ -100,16 +118,20 @@ Plug.Cowboy.https MyPlug, [], ## HTTP Strict Transport Security (HSTS) -Once a server is configured to support HTTPS it is often a good idea to redirect HTTP requests to HTTPS. To do this, include Plug.SSL in your Plug pipeline. +Once a server is configured to support HTTPS it is often a good idea to redirect HTTP requests to HTTPS. To do this, include `Plug.SSL` in the Plug pipeline. + +To prevent downgrade attacks, in which an attacker intercepts a plain HTTP request to the server before the redirect to HTTPS takes place, Plug.SSL by default sets the '[Strict-Transport-Security](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet)' (HSTS) header. This informs the browser that the current site must only ever be accessed over HTTPS, even if the user typed or clicked a plain HTTP URL. This only works if the site is reachable on port 443 (see [Listening on Port 443](#listening-on-port-443), below). + +Note that it is very difficult, if not impossible, to revert the effect of HSTS before the entry stored in the browser expires! Consider using a short `expires` value initially, and increasing it to a large value (e.g. 31536000 seconds for 1 year) after testing. -To prevent downgrade attacks, in which an attacker tricks a victim into connecting over plain HTTP even if the server would normally redirect that user to HTTPS, Plug.SSL by default sets the 'Strict-Transport-Security' header to mark the domain as HTTPS-only in the browser. To disable this behaviour, pass `hsts: false` to Plug.SSL. +The Strict-Transport-Security header can be disabled altogether by setting `hsts: false` in the `Plug.SSL` options. ## Encrypted Keys -To protect the private key on disk it is best to store it in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `password` option: +To protect the private key on disk it is best stored in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `password` option: ```elixir -Plug.Cowboy.https MyPlug, [], +Plug.Cowboy.https MyApp.MyPlug, [], port: 8443, certfile: "/etc/letsencrypt/live/example.net/cert.pem", keyfile: "/etc/letsencrypt/live/example.net/privkey_aes.pem", @@ -117,19 +139,19 @@ Plug.Cowboy.https MyPlug, [], password: "SECRET" ``` -To encrypt an existing PEM-encoded RSA key use the OpenSSL CLI: `openssl rsa -in privkey.pem -out privkey_aes.pem -aes128`. Use `ec` instead of `rsa` when using an ECDSA certificate. Don't forget to securely erase the unencrypted copy afterwards! Even better would be to encrypt the file during initial key generation: please refer to the instructions provided by your CA. +To encrypt an existing PEM-encoded RSA key use the OpenSSL CLI: `openssl rsa -in privkey.pem -out privkey_aes.pem -aes128`. Use `ec` instead of `rsa` when using an ECDSA certificate. Don't forget to securely erase the unencrypted copy afterwards! Best practice would be to encrypt the file immediately during initial key generation: please refer to the instructions provided by the CA. It is important to note that, at the time of writing, Erlang/OTP does not support keys encrypted with AES-256. The OpenSSL command in the previous paragraph can also be used to convert an AES-256 encrypted key to AES-128. ## Passing DER Binaries -Sometimes it is preferable to not store the private key on disk at all. Instead, the private key might be passed to your application using an environment variable or retrieved from a key store such as Vault. +Sometimes it is preferable to not store the private key on disk at all. Instead, the private key might be passed to the application using an environment variable or retrieved from a key store such as Vault. In such cases it is possible to pass the private key directly, using the `key` parameter. For example, assuming an RSA private key is available in the PRIVKEY environment variable in Base64 encoded DER format, the key may be set as follows: ```elixir der = System.get_env("PRIVKEY") |> Base.decode64! -Plug.Cowboy.https MyPlug, [], +Plug.Cowboy.https MyApp.MyPlug, [], port: 8443, key: {:RSAPrivateKey, der}, #.... @@ -145,11 +167,21 @@ It is recommended to generate a custom set of Diffie Hellman parameters, to be u `openssl dhparam -out dhparams.pem 4096` -On a slow machine (e.g. a cheap VPS) this may take several hours. You may want to run the command on a strong machine and copy the file over to the target server: the file does not need to be kept secret. +On a slow machine (e.g. a cheap VPS) this may take several hours. You may want to run the command on a strong machine and copy the file over to the target server: the file does not need to be kept secret. It is best practice to rotate the file periodically. -Pass the (relative or absolute) path using the `dhfile` option. It is best practice to rotate the file periodically. +Pass the (relative or absolute) path using the `dhfile` option: -If no custom parameters are specified, Erlang's 'ssl' uses its built-in defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC 3526. +```elixir +Plug.Cowboy.https MyApp.MyPlug, [], + port: 8443, + cipher_suite: :strong, + certfile: "priv/cert/selfsigned.pem", + keyfile: "priv/cert/selfsigned_key.pem", + dhfile: "priv/cert/dhparams.pem", + otp_app: :my_app +``` + +If no custom parameters are specified, Erlang's 'ssl' uses its built-in defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC3526. ## Renewing Certificates @@ -157,14 +189,16 @@ TODO ## Listening on Port 443 -By default clients expect HTTPS servers to listen on port 443. It is possible to specify a different port in HTTPS URLs, but for public servers it is often preferable to stick to the default. This presents a problem, however: only privileged processes can bind to TCP port numbers under 1024, and it is bad idea to run your Plug application as 'root'. +By default clients expect HTTPS servers to listen on port 443. It is possible to specify a different port in HTTPS URLs, but for public servers it is often preferable to stick to the default. In particular, HSTS requires that the site is reachable throug HTTPS on port 443. + +This presents a problem, however: only privileged processes can bind to TCP port numbers under 1024, and it is bad idea to run the application as 'root'. Leaving aside solutions that rely on external network elements, such as load balancers, there are a few solutions on typical Linux servers: -* Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see also 'Offloading TLS', below); the proxy will listen on port 443 and pass the traffic to the Elixir application running on an unprivileged port +* Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see [Offloading TLS](#offloading-tls), below); the proxy will listen on port 443 and pass the traffic to the Elixir application running on an unprivileged port * Create an IPTables rule to forward packets arriving on port 443 to the port on which the Elixir application is running * Give the Erlang/OTP runtime (that is, the BEAM VM executable) permission to bind to privileged ports using 'setcap', e.g. `sudo setcap 'cap_net_bind_service=+ep' /usr/lib/erlang/erts-10.1/bin/beam.smp`; update the path as necessary, and remember to run the command again after Erlang upgrades -* Use a tools such as 'authbind' to give an unprivileged user/group permission to bind to specific ports +* Use a tool such as 'authbind' to give an unprivileged user/group permission to bind to specific ports This is not intended to be an exhaustive list, as this topic is actually a bit beyond the scope of the current document. The issue is a generic one, not specific to Erlang/Elixir, and a lot of documentation can be found online. @@ -172,15 +206,22 @@ This is not intended to be an exhaustive list, as this topic is actually a bit b So far this document has focussed on configuring Plug to handle TLS within the application. Some people instead prefer to terminate TLS in a proxy or load balancer deployed in front of the Plug application. +### Pros and Cons + +Offloading might be done to achieve higher throughput, or to stick to the more widely used OpenSSL implementation of the TLS protocol. The Erlang/OTP implementation depends on OpenSSL for the underlying cryptography, but it implements its own message framing and protocol state machine. While it is not clear that one implementation is inherently more secure than the other, it might be advantageous to just patch OpenSSL along with everybody else, rather than having to research the implications of a new TLS vulnerability on the Erlang/OTP implementation. + +On the other hand, the proxy solution might not support end-to-end HTTP 2, limiting the benefits of the new protocol. It can also introduce operational complexities and new resource constraints, especially for long-lived connections such as WebSockets. + +### Plug Configuration Impact + TODO: -- Pros and cons - X-Forwarded-For and X-Forwarded-Proto - Using Plug.SSL - 'Secure' cookies ## Converting Certificates and Keys -If your certificate and/or key files are not in PEM format, here are some OpenSSL commands you may use to convert the files for use with Plug. +When certificate and/or key files are not in provided in PEM format they can usually be converted using the OpenSSL CLI. This section describes some common formats and the associated OpenSSL commands to convert to PEM. ### From DER to PEM @@ -196,6 +237,6 @@ A DER private key may also be stored in a PKCS#8 container, which may be passwor The PKCS#12 format is a container format potentially containing multiple certificates and/or encrypted keys. Such files typically have a `.p12` extension. -To extract all certificates from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nokeys -out fullchain.pem`. The resulting file contains all certificates from the input file, typically the server certificate and any CA certificates that make up the CA chain. You can split the file into seperate `cert.pem` and `chain.pem` files using a text editor, or you can just pass `certfile: fullchain.pem` to the HTTPS adapter (Erlang's ssl will look for the CA chain in the certificate file if no separate `cacertfile` is specified). +To extract all certificates from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nokeys -out fullchain.pem`. The resulting file contains all certificates from the input file, typically the server certificate and any CA certificates that make up the CA chain. You can split the file into seperate `cert.pem` and `chain.pem` files using a text editor, or you can just pass `certfile: fullchain.pem` to the HTTPS adapter. To extract an RSA private key from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nocerts -nodes -out privkey.pem`. You may want to replace the `-nodes` argument with `-aes128` to produce an encrypted, password protected PEM file. From f6a8b90b0164cc074be4d293bbd7af55f1b7c3b4 Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Wed, 19 Dec 2018 11:52:25 +0200 Subject: [PATCH 3/8] Third draft of HTTPS Guide --- guides/https.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/guides/https.md b/guides/https.md index 632eb43c..c95ebbd5 100644 --- a/guides/https.md +++ b/guides/https.md @@ -185,7 +185,15 @@ If no custom parameters are specified, Erlang's 'ssl' uses its built-in defaults ## Renewing Certificates -TODO +Whenever a certificate is about to expire, when the contents of the certificate has been updated, or when the certificate is 're-keyed', the HTTPS server needs to be updated with the new certificate and/or key. + +When using the `certfile` and `keyfile` parameters to reference PEM files on disk, replacing the certificate and key is as simple as overwriting the files. Erlang's 'ssl' application periodically checks the timestamps of such files, and reloads them if it detects a change. It may be best to use symbolic links that point to versioned copies of the files, to allow for quick rollback in case of problems. + +Note that there is a potential race condition when both the certificate and the key need to be replaced at the same time: if the 'ssl' application detects the change of one file before the other file is updated, the partial update can leave the HTTPS server with a mismatched private key. This can be avoiding by placing the private key in the same PEM file as the certificate, and omitting the `keyfile` parameter. This configuration allows atomic updates, and it works because 'ssl' looks for a private key entry in the `certfile` PEM file if no `key` or `keyfile` option is specified. + +Also note that some filesystems, such as network and container filesystems or VM-mounted volumes, may not support reliable detection of file changes through metadata. + +While it is possible to update the DER binaries passed in the `cert` or `key` options (as well as any other TLS protocol parameters) at runtime, this requires knowledge of the internals of the Plug adapter being used, and is therefore beyond the scope of this document. ## Listening on Port 443 @@ -195,7 +203,7 @@ This presents a problem, however: only privileged processes can bind to TCP port Leaving aside solutions that rely on external network elements, such as load balancers, there are a few solutions on typical Linux servers: -* Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see [Offloading TLS](#offloading-tls), below); the proxy will listen on port 443 and pass the traffic to the Elixir application running on an unprivileged port +* Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see [Offloading TLS](#offloading-tls), below); the proxy listens on port 443 and passes the traffic to the Elixir application running on an unprivileged port * Create an IPTables rule to forward packets arriving on port 443 to the port on which the Elixir application is running * Give the Erlang/OTP runtime (that is, the BEAM VM executable) permission to bind to privileged ports using 'setcap', e.g. `sudo setcap 'cap_net_bind_service=+ep' /usr/lib/erlang/erts-10.1/bin/beam.smp`; update the path as necessary, and remember to run the command again after Erlang upgrades * Use a tool such as 'authbind' to give an unprivileged user/group permission to bind to specific ports @@ -208,16 +216,19 @@ So far this document has focussed on configuring Plug to handle TLS within the a ### Pros and Cons -Offloading might be done to achieve higher throughput, or to stick to the more widely used OpenSSL implementation of the TLS protocol. The Erlang/OTP implementation depends on OpenSSL for the underlying cryptography, but it implements its own message framing and protocol state machine. While it is not clear that one implementation is inherently more secure than the other, it might be advantageous to just patch OpenSSL along with everybody else, rather than having to research the implications of a new TLS vulnerability on the Erlang/OTP implementation. +Offloading might be done to achieve higher throughput, or to stick to the more widely used OpenSSL implementation of the TLS protocol. The Erlang/OTP implementation depends on OpenSSL for the underlying cryptography, but it implements its own message framing and protocol state machine. While it is not clear that one implementation is inherently more secure than the other, just patching OpenSSL along with everybody else in case of vulnerabilities might give peace of mind, compared to than having to research the implications on the Erlang/OTP implementation. On the other hand, the proxy solution might not support end-to-end HTTP 2, limiting the benefits of the new protocol. It can also introduce operational complexities and new resource constraints, especially for long-lived connections such as WebSockets. ### Plug Configuration Impact -TODO: -- X-Forwarded-For and X-Forwarded-Proto -- Using Plug.SSL -- 'Secure' cookies +When using TLS offloading it may be necessary to make some configuration changes to the application. + +The `remote_ip` field in the `Plug.Conn` struct by default contains the network peer IP address. Terminating TLS in a separate process or network element typically masks the actual client IP address from the Elixir application. If proxying is done at the HTTP layer, the original client IP address is often inserted into an HTTP header, e.g. 'X-Forwarded-For'. There are Plug packages available to extract the client IP from such a header and update the `remote_ip` field. Please beware of, and mitigate, the security risk of clients spoofing an IP address by including this header in their original request! + +For solutions that operate below the HTTP layer, e.g. using HAProxy, the client IP address can sometimes be passed through the 'PROXY protocol'. Extracting this information must be handled by the Plug adapter. Please refer to the Plug adapter documentation for further information. + +`Plug.SSL` takes on another important role when using TLS offloading: it can update the `scheme` field in the `Plug.Conn` struct based on an HTTP header (e.g. 'X-Forwarded-Proto'), to reflect the actual protocol used by the client (HTTP or HTTPS). It is very important that the `scheme` field properly reflects the use of HTTPS, even if the connection between the proxy and the application uses plain HTTP, because cookies set by `Plug.Session` and `Plug.Conn.put_resp_cookie/4` by default set the 'secure' cookie flag only if `scheme` is set to `:https`! When relying on this default behaviour it is essential that `Plug.SSL` is included in the Plug pipeline, that its `rewrite_on` option is set correctly, and that the proxy sets the appropriate header. ## Converting Certificates and Keys From 8a1efad6291d2aca828719217903576c4fd0ab46 Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Wed, 19 Dec 2018 14:07:40 +0200 Subject: [PATCH 4/8] Simplify Plug.SSL docs and reference HTTPS guide --- lib/plug/ssl.ex | 83 ++----------------------------------------------- 1 file changed, 2 insertions(+), 81 deletions(-) diff --git a/lib/plug/ssl.ex b/lib/plug/ssl.ex index 169a7878..ed4c0e57 100644 --- a/lib/plug/ssl.ex +++ b/lib/plug/ssl.ex @@ -103,49 +103,8 @@ defmodule Plug.SSL do Plug users are not expected to invoke it directly, rather you pass the relevant SSL options to your adapter which then invokes this. - For instance, here is how you would pass the SSL options to the Cowboy - adapter: - - Plug.Cowboy.https MyPlug, [], - port: 443, - password: "SECRET", - otp_app: :my_app, - cipher_suite: :strong, - keyfile: "priv/ssl/key.pem", - certfile: "priv/ssl/cert.pem", - dhfile: "priv/ssl/dhparam.pem" - - or using the new child spec API: - - {Plug.Cowboy, scheme: :https, plug: MyPlug, options: [ - port: 443, - password: "SECRET", - otp_app: :my_app, - cipher_suite: :strong, - keyfile: "priv/ssl/key.pem", - certfile: "priv/ssl/cert.pem", - dhfile: "priv/ssl/dhparam.pem" - ]} - - ## Options - - This function accepts and validates all options defined in [the `ssl` - erlang module](http://www.erlang.org/doc/man/ssl.html). With the - following additions: - - * The `:cipher_suite` option provides `:strong` and `:compatible` - options for setting up better cipher and version defaults according - to the OWASP recommendations. See the "Cipher Suites" section below - - * The certificate files, like keyfile, certfile, cacertfile, dhfile - can be given as a relative path. For such, the `:otp_app` option - must also be given and certificates will be looked from the priv - directory of the given application - - * In order to provide better security, this function also enables - `:reuse_sessions` and `:secure_renegotiate` by default, to instruct - clients to reuse sessions and enforce secure renegotiation according - to RFC 5746 respectively + For further information, please refer to the adapter documentation and + the [HTTPS Guide](https.html). ## Cipher Suites @@ -174,45 +133,7 @@ defmodule Plug.SSL do Since OWASP doesn't prescribe curves we've based the selection on the following Mozilla recommendations: https://wiki.mozilla.org/Security/Server_Side_TLS#Cipher_names_correspondence_table - In addition to selecting a group of ciphers, selecting a cipher suite will also - force the client to honor the server specified cipher order. - - Any of those choices can be disabled on a per choice basis by specifying the - equivalent SSL option alongside the cipher suite. - **The cipher suites were last updated on 2018-JUN-14.** - - ## Manual Cipher Configuration - - Should you choose to configure your own ciphers you cannot use the `:cipher_suite` option - as setting a cipher suite overrides your cipher selections. - - Instead, you can see the valid options for ciphers in the Erlang SSL documentation: - http://erlang.org/doc/man/ssl.html - - Please note that specifying a cipher as a binary string is not valid and would silently fail in the past. - This was problematic because the result would be for Erlang to use the default list of ciphers. - To prevent this Plug will now throw an error to ensure you're aware of this. - - ## Diffie Hellman parameters - - It is recommended to generate a custom set of Diffie Hellman parameters, to be - used for the DHE key exchange. Use the following OpenSSL CLI command to create - a 'dhparam.pem' file: - - openssl dhparam -out dhparam.pem 4096 - - On a slow machine (e.g. a cheap VPS) this may take several hours. You may want - to run the command on a strong machine and copy the file over: the file does - not need to be kept secret. - - Add the resulting file to your application's `priv` directory and pass the - path using the `:dhfile` key. It is best practice to rotate the file - periodically. - - If no custom parameters are specified, Erlang's `ssl` uses its built-in - defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC 3526. - """ @spec configure(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()} def configure(options) do From 3ffb03a55afcf845ddfe4a955e819bd46d352a95 Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Wed, 19 Dec 2018 14:12:07 +0200 Subject: [PATCH 5/8] Activate links in Plug.SSL docs --- lib/plug/ssl.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plug/ssl.ex b/lib/plug/ssl.ex index ed4c0e57..8326d797 100644 --- a/lib/plug/ssl.ex +++ b/lib/plug/ssl.ex @@ -110,8 +110,8 @@ defmodule Plug.SSL do To simplify configuration of TLS defaults Plug provides two preconfigured options: `cipher_suite: :strong` and `cipher_suite: :compatible`. The Ciphers - chosen and related configuration come from the OWASP recommendations found here: - https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet + chosen and related configuration come from the [OWASP Cipher String Cheat + Sheet](https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet) We've made two modifications to the suggested config from the OWASP recommendations. First we include ECDSA certificates which are excluded from their configuration. @@ -130,8 +130,8 @@ defmodule Plug.SSL do still maintain support for older browsers and Android versions 4.3 and earlier For both suites we've specified certificate curves secp256r1, ecp384r1 and secp521r1. - Since OWASP doesn't prescribe curves we've based the selection on the following Mozilla - recommendations: https://wiki.mozilla.org/Security/Server_Side_TLS#Cipher_names_correspondence_table + Since OWASP doesn't prescribe curves we've based the selection on [Mozilla's + recommendations](https://wiki.mozilla.org/Security/Server_Side_TLS#Cipher_names_correspondence_table) **The cipher suites were last updated on 2018-JUN-14.** """ From 89a6d50e1b0fc31b15f28411aba74afb5f8eb9fa Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Tue, 25 Dec 2018 15:05:09 +0200 Subject: [PATCH 6/8] HTTPS guide formatting and language edits --- guides/https.md | 62 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/guides/https.md b/guides/https.md index c95ebbd5..ee44e2ae 100644 --- a/guides/https.md +++ b/guides/https.md @@ -4,7 +4,7 @@ Plug can serve HTTP over TLS ('HTTPS') through an appropriately configured Adapt This guide describes how to use these parameters to set up an HTTPS server with Plug, and documents some best-practices and potential pitfalls. -*Editor's note: The secure transport protocol used by HTTPS is nowadays referred to as TLS. However, the application in the Erlang/OTP standard library that implements it is called 'ssl', for historical reasons. In this document we will refer to the protocol as 'TLS' and to the Erlang/OTP implementation as 'ssl', and its configuration parameters as 'ssl options'.* +> Editor's note: The secure transport protocol used by HTTPS is nowadays referred to as TLS. However, the application in the Erlang/OTP standard library that implements it is called `:ssl`, for historical reasons. In this document we will refer to the protocol as 'TLS' and to the Erlang/OTP implementation as `:ssl`, and its configuration parameters as `:ssl` options. ## Prerequisites @@ -18,9 +18,9 @@ The prerequisites for running an HTTPS server with Plug include: For testing purposes it may be sufficient to use a self-signed certificate. Such certificates generally result in warnings in browsers and failed connections from other tools, but these can be overridden to enable HTTPS testing. This is especially useful for local testing of HTTP 2, which is only specified over TLS. -**Warning**: *use self-signed certificates only for local testing, and do not mark such test certificates as globally trusted in browsers or operating system!* +> **Warning**: use self-signed certificates only for local testing, and do not mark such test certificates as globally trusted in browsers or operating system! -The [Phoenix](https://phoenixframework.org/) project includes a Mix task `phx.gen.cert` that generates the necessary files and places them in the application's 'priv' directory. The [X509](https://hex.pm/packages/x509) package can be used as a dev-only dependency to add a similar `x509.gen.selfsigned` Mix task to non-Phoenix projects. +The [Phoenix](https://phoenixframework.org/) project includes a Mix task `mix phx.gen.cert` that generates the necessary files and places them in the application's 'priv' directory. The [X509](https://hex.pm/packages/x509) package can be used as a dev-only dependency to add a similar `mix x509.gen.selfsigned` task to non-Phoenix projects. Alternatively, the OpenSSL CLI or other utilities can be used to generate a self-signed certificate. Instructions are widely available online. @@ -68,10 +68,10 @@ Remember to exclude the files from version control, unless the certificate and k In addition to a certificate, an HTTPS server needs a secure TLS protocol configuration. `Plug.SSL` always sets the following options: -* Enable `secure_renegotiate`, to avoid certain types of man-in-the-middle attacks -* Enable `reuse_sessions`, for improved handshake performance of recurring connections +* Set `secure_renegotiate: true`, to avoid certain types of man-in-the-middle attacks +* Set `reuse_sessions: true`, for improved handshake performance of recurring connections -Additional options can be set by selecting a predefined profile or by setting 'ssl' options individually. +Additional options can be set by selecting a predefined profile or by setting `:ssl` options individually. ### Predefined Options @@ -84,20 +84,20 @@ The `:compatible` profile additionally enables AES-CBC ciphers, as well as TLS v In addition, both profiles: * Configure the server to choose a cipher based on its own preferences rather than the client's (`honor_cipher_order` set to `true`); when specifying a custom cipher list, ensure the ciphers are listed in descending order of preference -* Disable `client_renegotiation` +* Set `client_renegotiation: false` * Select the 'Prime' (SECP) curves for use in Elliptic Curve Cryptography (ECC) -All these parameters, including the global defaults mentioned above, can be overridden by specifying custom 'ssl' configuration options. +All these parameters, including the global defaults mentioned above, can be overridden by specifying custom `:ssl` configuration options. -It is worth noting that the cipher lists and TLS protocol versions selected by the profiles are whitelists. If a new Erlang/OTP release introduces new TLS protocol versions or ciphers that are not included in the profile definition, they would have to be enabled explicitly by overriding the `ciphers` and/or `versions` options, until such time as they are added to the Plug.SSL profiles. +It is worth noting that the cipher lists and TLS protocol versions selected by the profiles are whitelists. If a new Erlang/OTP release introduces new TLS protocol versions or ciphers that are not included in the profile definition, they would have to be enabled explicitly by overriding the `:ciphers` and/or `:versions` options, until such time as they are added to the `Plug.SSL` profiles. The ciphers chosen and related configuration are based on [OWASP recommendations](https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet), with some modifications as described in the `Plug.SSL.configure/1` documentation. ### Manual Configuration -Please refer to the [Erlang/OTP 'ssl' documentation](http://erlang.org/doc/man/ssl.html) for details on the supported configuration options. +Please refer to the [Erlang/OTP `:ssl` documentation](http://erlang.org/doc/man/ssl.html) for details on the supported configuration options. -An example configuration with custom 'ssl' options might look like this: +An example configuration with custom `:ssl` options might look like this: ```elixir Plug.Cowboy.https MyApp.MyPlug, [], @@ -120,15 +120,15 @@ Plug.Cowboy.https MyApp.MyPlug, [], Once a server is configured to support HTTPS it is often a good idea to redirect HTTP requests to HTTPS. To do this, include `Plug.SSL` in the Plug pipeline. -To prevent downgrade attacks, in which an attacker intercepts a plain HTTP request to the server before the redirect to HTTPS takes place, Plug.SSL by default sets the '[Strict-Transport-Security](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet)' (HSTS) header. This informs the browser that the current site must only ever be accessed over HTTPS, even if the user typed or clicked a plain HTTP URL. This only works if the site is reachable on port 443 (see [Listening on Port 443](#listening-on-port-443), below). +To prevent downgrade attacks, in which an attacker intercepts a plain HTTP request to the server before the redirect to HTTPS takes place, `Plug.SSL` by default sets the '[Strict-Transport-Security](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet)' (HSTS) header. This informs the browser that the current site must only ever be accessed over HTTPS, even if the user typed or clicked a plain HTTP URL. This only works if the site is reachable on port 443 (see [Listening on Port 443](#listening-on-port-443), below). -Note that it is very difficult, if not impossible, to revert the effect of HSTS before the entry stored in the browser expires! Consider using a short `expires` value initially, and increasing it to a large value (e.g. 31536000 seconds for 1 year) after testing. +> **Warning**: it is very difficult, if not impossible, to revert the effect of HSTS before the entry stored in the browser expires! Consider using a short `:expires` value initially, and increasing it to a large value (e.g. 31536000 seconds for 1 year) after testing. The Strict-Transport-Security header can be disabled altogether by setting `hsts: false` in the `Plug.SSL` options. ## Encrypted Keys -To protect the private key on disk it is best stored in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `password` option: +To protect the private key on disk it is best stored in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `:password` option: ```elixir Plug.Cowboy.https MyApp.MyPlug, [], @@ -141,13 +141,13 @@ Plug.Cowboy.https MyApp.MyPlug, [], To encrypt an existing PEM-encoded RSA key use the OpenSSL CLI: `openssl rsa -in privkey.pem -out privkey_aes.pem -aes128`. Use `ec` instead of `rsa` when using an ECDSA certificate. Don't forget to securely erase the unencrypted copy afterwards! Best practice would be to encrypt the file immediately during initial key generation: please refer to the instructions provided by the CA. -It is important to note that, at the time of writing, Erlang/OTP does not support keys encrypted with AES-256. The OpenSSL command in the previous paragraph can also be used to convert an AES-256 encrypted key to AES-128. +> Note: at the time of writing, Erlang/OTP does not support keys encrypted with AES-256. The OpenSSL command in the previous paragraph can also be used to convert an AES-256 encrypted key to AES-128. ## Passing DER Binaries Sometimes it is preferable to not store the private key on disk at all. Instead, the private key might be passed to the application using an environment variable or retrieved from a key store such as Vault. -In such cases it is possible to pass the private key directly, using the `key` parameter. For example, assuming an RSA private key is available in the PRIVKEY environment variable in Base64 encoded DER format, the key may be set as follows: +In such cases it is possible to pass the private key directly, using the `:key` parameter. For example, assuming an RSA private key is available in the PRIVKEY environment variable in Base64 encoded DER format, the key may be set as follows: ```elixir der = System.get_env("PRIVKEY") |> Base.decode64! @@ -159,17 +159,17 @@ Plug.Cowboy.https MyApp.MyPlug, [], Note that reading environment variables in Mix config files only works when starting the application using Mix, e.g. in a development environment. In production, a different approach is needed for runtime configuration, but this is out of scope for the current document. -The certificate and CA chain can also be specified using DER binaries, using the `cert` and `cacerts` options, but this is best avoided. The use of PEM files has been tested much more thoroughly with the Erlang/OTP 'ssl' application, and there have been a number of issues with DER binary certificates in the past. +The certificate and CA chain can also be specified using DER binaries, using the `:cert` and `:cacerts` options, but this is best avoided. The use of PEM files has been tested much more thoroughly with the Erlang/OTP `:ssl` application, and there have been a number of issues with DER binary certificates in the past. ## Custom Diffie-Hellman Parameters -It is recommended to generate a custom set of Diffie Hellman parameters, to be used for the DHE key exchange. Use the following OpenSSL CLI command to create a 'dhparam.pem' file: +It is recommended to generate a custom set of Diffie-Hellman parameters, to be used for the DHE key exchange. Use the following OpenSSL CLI command to create a `dhparam.pem` file: `openssl dhparam -out dhparams.pem 4096` On a slow machine (e.g. a cheap VPS) this may take several hours. You may want to run the command on a strong machine and copy the file over to the target server: the file does not need to be kept secret. It is best practice to rotate the file periodically. -Pass the (relative or absolute) path using the `dhfile` option: +Pass the (relative or absolute) path using the `:dhfile` option: ```elixir Plug.Cowboy.https MyApp.MyPlug, [], @@ -181,34 +181,34 @@ Plug.Cowboy.https MyApp.MyPlug, [], otp_app: :my_app ``` -If no custom parameters are specified, Erlang's 'ssl' uses its built-in defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC3526. +If no custom parameters are specified, Erlang's `:ssl` uses its built-in defaults. Since OTP 19 this has been the 2048-bit 'group 14' from RFC3526. ## Renewing Certificates Whenever a certificate is about to expire, when the contents of the certificate has been updated, or when the certificate is 're-keyed', the HTTPS server needs to be updated with the new certificate and/or key. -When using the `certfile` and `keyfile` parameters to reference PEM files on disk, replacing the certificate and key is as simple as overwriting the files. Erlang's 'ssl' application periodically checks the timestamps of such files, and reloads them if it detects a change. It may be best to use symbolic links that point to versioned copies of the files, to allow for quick rollback in case of problems. +When using the `:certfile` and `:keyfile` parameters to reference PEM files on disk, replacing the certificate and key is as simple as overwriting the files. Erlang's `:ssl` application periodically checks the timestamps of such files, and reloads them if it detects a change. It may be best to use symbolic links that point to versioned copies of the files, to allow for quick rollback in case of problems. -Note that there is a potential race condition when both the certificate and the key need to be replaced at the same time: if the 'ssl' application detects the change of one file before the other file is updated, the partial update can leave the HTTPS server with a mismatched private key. This can be avoiding by placing the private key in the same PEM file as the certificate, and omitting the `keyfile` parameter. This configuration allows atomic updates, and it works because 'ssl' looks for a private key entry in the `certfile` PEM file if no `key` or `keyfile` option is specified. +Note that there is a potential race condition when both the certificate and the key need to be replaced at the same time: if the `:ssl` application detects the change of one file before the other file is updated, the partial update can leave the HTTPS server with a mismatched private key. This can be avoiding by placing the private key in the same PEM file as the certificate, and omitting the `:keyfile` option. This configuration allows atomic updates, and it works because `:ssl` looks for a private key entry in the `:certfile` PEM file if no `:key` or `:keyfile` option is specified. Also note that some filesystems, such as network and container filesystems or VM-mounted volumes, may not support reliable detection of file changes through metadata. -While it is possible to update the DER binaries passed in the `cert` or `key` options (as well as any other TLS protocol parameters) at runtime, this requires knowledge of the internals of the Plug adapter being used, and is therefore beyond the scope of this document. +While it is possible to update the DER binaries passed in the `:cert` or `:key` options (as well as any other TLS protocol parameters) at runtime, this requires knowledge of the internals of the Plug adapter being used, and is therefore beyond the scope of this document. ## Listening on Port 443 -By default clients expect HTTPS servers to listen on port 443. It is possible to specify a different port in HTTPS URLs, but for public servers it is often preferable to stick to the default. In particular, HSTS requires that the site is reachable throug HTTPS on port 443. +By default clients expect HTTPS servers to listen on port 443. It is possible to specify a different port in HTTPS URLs, but for public servers it is often preferable to stick to the default. In particular, HSTS requires that the site be reachable on port 443 using HTTPS. This presents a problem, however: only privileged processes can bind to TCP port numbers under 1024, and it is bad idea to run the application as 'root'. -Leaving aside solutions that rely on external network elements, such as load balancers, there are a few solutions on typical Linux servers: +Leaving aside solutions that rely on external network elements, such as load balancers, there are several solutions on typical Linux servers: * Deploy a reverse proxy or load balancer process, such as Nginx or HAProxy (see [Offloading TLS](#offloading-tls), below); the proxy listens on port 443 and passes the traffic to the Elixir application running on an unprivileged port * Create an IPTables rule to forward packets arriving on port 443 to the port on which the Elixir application is running * Give the Erlang/OTP runtime (that is, the BEAM VM executable) permission to bind to privileged ports using 'setcap', e.g. `sudo setcap 'cap_net_bind_service=+ep' /usr/lib/erlang/erts-10.1/bin/beam.smp`; update the path as necessary, and remember to run the command again after Erlang upgrades * Use a tool such as 'authbind' to give an unprivileged user/group permission to bind to specific ports -This is not intended to be an exhaustive list, as this topic is actually a bit beyond the scope of the current document. The issue is a generic one, not specific to Erlang/Elixir, and a lot of documentation can be found online. +This is not intended to be an exhaustive list, as this topic is actually a bit beyond the scope of the current document. The issue is a generic one, not specific to Erlang/Elixir, and further explanations can be found online. ## Offloading TLS @@ -224,11 +224,13 @@ On the other hand, the proxy solution might not support end-to-end HTTP 2, limit When using TLS offloading it may be necessary to make some configuration changes to the application. -The `remote_ip` field in the `Plug.Conn` struct by default contains the network peer IP address. Terminating TLS in a separate process or network element typically masks the actual client IP address from the Elixir application. If proxying is done at the HTTP layer, the original client IP address is often inserted into an HTTP header, e.g. 'X-Forwarded-For'. There are Plug packages available to extract the client IP from such a header and update the `remote_ip` field. Please beware of, and mitigate, the security risk of clients spoofing an IP address by including this header in their original request! +`Plug.SSL` takes on another important role when using TLS offloading: it can update the `:scheme` field in the `Plug.Conn` struct based on an HTTP header (e.g. 'X-Forwarded-Proto'), to reflect the actual protocol used by the client (HTTP or HTTPS). It is very important that the `:scheme` field properly reflects the use of HTTPS, even if the connection between the proxy and the application uses plain HTTP, because cookies set by `Plug.Session` and `Plug.Conn.put_resp_cookie/4` by default set the 'secure' cookie flag only if `:scheme` is set to `:https`! When relying on this default behaviour it is essential that `Plug.SSL` is included in the Plug pipeline, that its `:rewrite_on` option is set correctly, and that the proxy sets the appropriate header. -For solutions that operate below the HTTP layer, e.g. using HAProxy, the client IP address can sometimes be passed through the 'PROXY protocol'. Extracting this information must be handled by the Plug adapter. Please refer to the Plug adapter documentation for further information. +The `:remote_ip` field in the `Plug.Conn` struct by default contains the network peer IP address. Terminating TLS in a separate process or network element typically masks the actual client IP address from the Elixir application. If proxying is done at the HTTP layer, the original client IP address is often inserted into an HTTP header, e.g. 'X-Forwarded-For'. There are Plug packages available to extract the client IP from such a header and update the `:remote_ip` field. + +> **Warning**: ensure that clients cannot spoof their IP address by including this header in their original request, by filtering such headers in the proxy! -`Plug.SSL` takes on another important role when using TLS offloading: it can update the `scheme` field in the `Plug.Conn` struct based on an HTTP header (e.g. 'X-Forwarded-Proto'), to reflect the actual protocol used by the client (HTTP or HTTPS). It is very important that the `scheme` field properly reflects the use of HTTPS, even if the connection between the proxy and the application uses plain HTTP, because cookies set by `Plug.Session` and `Plug.Conn.put_resp_cookie/4` by default set the 'secure' cookie flag only if `scheme` is set to `:https`! When relying on this default behaviour it is essential that `Plug.SSL` is included in the Plug pipeline, that its `rewrite_on` option is set correctly, and that the proxy sets the appropriate header. +For solutions that operate below the HTTP layer, e.g. using HAProxy, the client IP address can sometimes be passed through the 'PROXY protocol'. Extracting this information must be handled by the Plug adapter. Please refer to the Plug adapter documentation for further information. ## Converting Certificates and Keys @@ -246,7 +248,7 @@ A DER private key may also be stored in a PKCS#8 container, which may be passwor ### From PKCS#12 to PEM -The PKCS#12 format is a container format potentially containing multiple certificates and/or encrypted keys. Such files typically have a `.p12` extension. +The PKCS#12 format is a container format containing one or more certificates and/or encrypted keys. Such files typically have a `.p12` extension. To extract all certificates from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nokeys -out fullchain.pem`. The resulting file contains all certificates from the input file, typically the server certificate and any CA certificates that make up the CA chain. You can split the file into seperate `cert.pem` and `chain.pem` files using a text editor, or you can just pass `certfile: fullchain.pem` to the HTTPS adapter. From 3c8f7c9a88df5fc1dd0824771470962d0a0c2aab Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Mon, 31 Dec 2018 14:47:21 +0200 Subject: [PATCH 7/8] Update HTTPS guide with latest changes --- guides/https.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guides/https.md b/guides/https.md index ee44e2ae..2ad7f871 100644 --- a/guides/https.md +++ b/guides/https.md @@ -84,7 +84,6 @@ The `:compatible` profile additionally enables AES-CBC ciphers, as well as TLS v In addition, both profiles: * Configure the server to choose a cipher based on its own preferences rather than the client's (`honor_cipher_order` set to `true`); when specifying a custom cipher list, ensure the ciphers are listed in descending order of preference -* Set `client_renegotiation: false` * Select the 'Prime' (SECP) curves for use in Elliptic Curve Cryptography (ECC) All these parameters, including the global defaults mentioned above, can be overridden by specifying custom `:ssl` configuration options. From 18d63d331adcf06636249bad5e358d5596ea248a Mon Sep 17 00:00:00 2001 From: Bram Verburg Date: Mon, 7 Jan 2019 08:53:53 +0200 Subject: [PATCH 8/8] Minor HTTPS guide improvements --- guides/https.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/guides/https.md b/guides/https.md index 2ad7f871..88b920eb 100644 --- a/guides/https.md +++ b/guides/https.md @@ -127,7 +127,7 @@ The Strict-Transport-Security header can be disabled altogether by setting `hsts ## Encrypted Keys -To protect the private key on disk it is best stored in encrypted PEM format, also known as PKCS#5. When configuring a Plug server with an encrypted private key, specify the password using the `:password` option: +To protect the private key on disk it is best stored in encrypted PEM format, protected by a password. When configuring a Plug server with an encrypted private key, specify the password using the `:password` option: ```elixir Plug.Cowboy.https MyApp.MyPlug, [], @@ -184,7 +184,7 @@ If no custom parameters are specified, Erlang's `:ssl` uses its built-in default ## Renewing Certificates -Whenever a certificate is about to expire, when the contents of the certificate has been updated, or when the certificate is 're-keyed', the HTTPS server needs to be updated with the new certificate and/or key. +Whenever a certificate is about to expire, when the contents of the certificate have been updated, or when the certificate is 're-keyed', the HTTPS server needs to be updated with the new certificate and/or key. When using the `:certfile` and `:keyfile` parameters to reference PEM files on disk, replacing the certificate and key is as simple as overwriting the files. Erlang's `:ssl` application periodically checks the timestamps of such files, and reloads them if it detects a change. It may be best to use symbolic links that point to versioned copies of the files, to allow for quick rollback in case of problems. @@ -241,9 +241,7 @@ DER-encoded files contain binary data. Common file extensions are `.crt` for cer To convert a single DER-encoded certificate to PEM format: `openssl x509 -in server.crt -inform der -out cert.pem` -To convert an RSA private key from DER to PEM format: `openssl rsa -in privkey.der -inform der -out privkey.pem`. You may want to add the `-aes128` argument to produce an encrypted, password protected PEM file. - -A DER private key may also be stored in a PKCS#8 container, which may be password protected. Such files sometimes have a `.p8` extension. In such cases use the following command to convert the private key to PEM format: `openssl pkcs8 -in privkey.p8 -inform der -out privkey.pem`. Again, add the `-aes128` argument if desired. +To convert an RSA private key from DER to PEM format: `openssl rsa -in privkey.der -inform der -out privkey.pem`. If the private key is a Elliptic Curve key, for use with an ECDSA certificate, replace `rsa` with `ec`. You may want to add the `-aes128` argument to produce an encrypted, password protected PEM file. ### From PKCS#12 to PEM @@ -251,4 +249,4 @@ The PKCS#12 format is a container format containing one or more certificates and To extract all certificates from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nokeys -out fullchain.pem`. The resulting file contains all certificates from the input file, typically the server certificate and any CA certificates that make up the CA chain. You can split the file into seperate `cert.pem` and `chain.pem` files using a text editor, or you can just pass `certfile: fullchain.pem` to the HTTPS adapter. -To extract an RSA private key from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nocerts -nodes -out privkey.pem`. You may want to replace the `-nodes` argument with `-aes128` to produce an encrypted, password protected PEM file. +To extract a private key from a PKCS#12 file to a PEM file: `openssl pkcs12 -in server.p12 -nocerts -nodes -out privkey.pem`. You may want to replace the `-nodes` argument with `-aes128` to produce an encrypted, password protected PEM file.