TLS Hardening Guide

Copy Markdown View Source

This guide describes how to harden Transport Layer Security (TLS) connections configured with the ssl application. It covers cryptographic algorithm selection, protocol version configuration, certificate verification, and revocation checking.

Note

There can exist trade-offs between interoperability and security, and between resource consumption and security. If you make such trade-offs it is important that you make an informed decision and understand the consequences. Also this guide will always reflect the status of defaults for the release where it is published, unless otherwise specifically stated. Hence using older releases may need more configuration to achieve the same result.

Erlang's TLS implementation provided by the ssl application strives to have secure defaults. That said, we will avoid changing defaults in non-major releases.

For embedded systems with constrained bandwidth, cipher suites using Advanced Encryption Standard (AES) with counter cipher block chaining message authentication code, that is AES-CCM-8 (8-byte authentication tag) can be configured and provides 64-bit integrity instead of 128-bit.

Warning

Be aware that enabling anything documented as legacy will make your application less secure.

Warning

Network Security Services Key Log Format (NSS Key Logging) is a debugging functionality that should not be enabled in production systems as it defeats the purpose of the TLS protocol.

Cryptographic Algorithms

The available cryptographic algorithms depend on the version of OpenSSL cryptolib that Erlang/OTP is built and linked with.

When you configure algorithm options such as supported_groups, or signature_algs, the ssl application automatically removes any algorithms not supported by the underlying OpenSSL cryptolib. The exception is the ciphers option: user-supplied cipher suites are not automatically filtered against crypto support, this is due to the more complex composition of algorithms in cipher suites pre TLS-1.3. In this case use ssl:filter_cipher_suites/2 to remove suites that are not supported by linked OpenSSL cryptolib.

Note

The TLS protocol is implemented in Erlang, and therefore only the cryptographic functions come from the OpenSSL cryptolib. This reduces the error surface by excluding buffer overflows and other pointer related errors that does not exist in Erlang programs.

Algorithms selection

The TLS protocol typically specifies that the client's algorithm preference determines the algorithm selection. The server option honor_cipher_order overrides this for cipher suite selection in all TLS versions. For TLS-1.2 and legacy versions, honor_ecc_order does the same for ECC curve selection.

Signature Algorithms

The signature_algs option configures which signature algorithms are acceptable for TLS protocol messages. The signature_algs_cert, option separately configures acceptable algorithms for certificate chain signatures.

If signature_algs is specified but not signature_algs_cert the value of signature_algs will implicitly be used for signature_algs_cert; however if none of them are explicitly specified certificate signatures are verified against default signature_algs (with additional legacy SHA-1 algorithms — see note below).

Note

Currently using the default signature_algs will add signature_algs_cert that allows signature algorithms using SHA-1 as pseudo random function for certificate signatures. This is not allowed by default for protocol signatures. It is expected to be removed after 2030, when such certificates should have been phased out.

Post Quantum Algorithms

Post-Quantum Cryptography (PQC) algorithms are designed to resist attacks from future cryptographically relevant quantum computers (CRQCs), which are expected to break algorithms such as RSA and ECC. PQC is only available in TLS-1.3.

Note

Even though CRQCs do not yet exist, enabling PQC protects against "harvest now, decrypt later" attacks where an adversary records encrypted traffic today for future decryption.

PQC algorithms generally require more computation and network bandwidth than classical algorithms. Key sizes, signatures, and ciphertexts are larger.

For key exchange, several ML-KEM hybrid groups are supported. By default, only x25519mlkem768 is offered — a hybrid that combines classical X25519 with ML-KEM-768 to balance current security with future quantum resistance, as the newer algorithms are less battle-tested.

For authentication, the signature algorithms ML-DSA and SLH-DSA are supported. ML-DSA is preferred due to faster signing and smaller signatures. Using PQC signature algorithms requires appropriate PQC certificates and keys.

Privacy and Integrity

The protocol version TLS-1.3 is a major upgrade of the protocol over previous versions, and supporting both TLS-1.3 and TLS-1.2 will require a configuration including options from both.

TLS 1.3 significantly improves session handling over TLS 1.2 by reducing the handshake from two round-trips (2-RTT) to one (1-RTT). It mandates forward secrecy, encrypts more of the handshake, and introduces 0-RTT resumption for previously visited sites.

The protocol itself will prevent version downgrade when using TLS-1.3 and TLS-1.2, which are the default supported versions. All other TLS versions still configurable are legacy versions. Datagram transport layer security, DTLS-1.2 over User Datagram Protocol (UDP) can also be configured and has functionality similar to TLS-1.2 but will always be less reliable and may lose application data just like plain UDP communication. DTLS over other transports can possibly be achieved but not out of the box. DTLS-1.3 is currently not supported.

Cipher Suites

TLS-1.3 does not share any cipher suites with previous protocol versions. You will need to include at least one cipher suite for both TLS-1.3 and TLS-1.2 to be able to support both versions in the ciphers option.

TLS 1.3 uses only modern Authenticated Encryption with Associated Data (AEAD) cipher suites, so does the ssl application's TLS-1.2 default.

Session Key Renewal

Renegotiation options only apply to versions prior to TLS-1.3. TLS-1.3 replaces renegotiation with the key update mechanism, which only handles session key rotation and does not allow renegotiating cipher suites or certificates mid-connection.

A server can disable client-initiated renegotiation with the client_renegotiation option to avoid possible DoS-attacks.

By default, the server mitigates renegotiation abuse by enforcing a 12-second delay between client initiated renegotiations.

Key Exchange Groups

TLS-1.3 decouples key exchange algorithms from cipher suites. The key exchange algorithms are configured using the supported_groups option. In TLS-1.2 in addition to the cipher suites the options eccs, dh, dhfile, are available for configuring the key exchange.

TLS 1.3 removes static RSA, supporting only ephemeral Diffie-Hellman, which provides mandatory Perfect Forward Secrecy (PFS), so does the ssl application's TLS-1.2 default.

Pre-Shared Keys

Warning

Do not use the same Pre-Shared Key (PSK) across both TLS 1.2 and TLS 1.3, as it is considered unsafe.

In TLS-1.2, PSK is supported by PSK cipher suites, which generally lack forward secrecy unless combined with DHE or ECDHE key exchange. In TLS-1.3, PSK are most commonly used together with a key exchange algorithm, providing forward secrecy, although a PSK only option still exists.

Early Data

Early data (0-RTT) in TLS-1.3 is not forward secret and is vulnerable to replay attacks. The server option early_data is disabled by default.

Warning

Only enable early data if your application can safely handle replayed requests (that is for instance, idempotent HTTP methods).

See Early Data in TLS-1.3 for mitigation strategies and examples.

Authenticity

Certificate verification is essential for establishing trust in TLS connections. The ssl application provides several mechanisms for customizing and strengthening certificate validation.

Certificate and Keys

Use the certs_keys option to be able to configure more than one possible certificate for the client or server. The best one compatible with the peer will be chosen.

Verify Function

The verify_fun option lets you customize certificate path validation for both TLS clients and servers. Use it to add application-specific checks beyond the standard chain verification.

Warning

However, the verify_fun can also be used to accept certain errors. Doing so is at your own risk and will weaken the security properties of your application. Legacy client verify option set to the value verify_none skips all certificate verification errors using a special verify_fun and should only be used for testing and debugging purposes.

Note

On the server side, verify_none is default as client certification is an optional feature of the protocol and it needs configuring. When client certification is configured, setting verify option to verify_peer, the server legacy option fail_if_no_peer_cert defaults to true.

To summarize:

  • Always use verify_peer on the client side (default since OTP 26)
  • Avoid accepting {bad_cert, _} errors in verify_fun unless there is some edge-case workaround that you have confidence in excluding.
  • When enabling client certification on the server, ensure fail_if_no_peer_cert is true (default since OTP 26).

Certificate Revocation Verification

Certificate revocation verification is important for security and needs to be performed. However it requires some application specific knowledge to set it up correctly, hence the option crl_check default to false. Use true to verify the entire certificate chain, or peer to verify only the peer certificate.

Warning

The best_effort value is legacy and insecure — it silently accepts certificates when revocation status cannot be determined.

The built-in Certificate Revocation List (CRL) cache can be pre-populated via ssl_crl_cache:insert/1 or configured to fetch CRLs from HTTP distribution points in certificates. For custom CRL sources, implement the ssl_crl_cache_api behaviour.

The client can request Online Certificate Status Protocol (OCSP) stapling from the server with the stapling option (disabled by default). In TLS-1.2, only end-entity certificate stapling is supported as opposed to TLS-1.3. If the server does not provide a staple, the connection by default fails with {bad_cert, missing_ocsp_staple}.

A custom verify_fun can intercept this to implement fallback revocation checking (that is, direct CRL fetch).

Warning

Accepting {bad_cert, missing_ocsp_staple} without performing alternative revocation checking is insecure, as it allows a MITM attacker to suppress revocation information by omitting the staple.

Note

The ssl application does not perform direct OCSP queries (client contacting the OCSP responder via the certificate's Authority Information Access (AIA) extension), only OCSP stapling (server-provided responses in the TLS handshake) is supported to be verified by the client. The Erlang server has no stapling support. Also, OCSP support seems to be on the decline, for instance Let's Encrypt has dropped it.