Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/ssl/doc/guides/ssl_distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,20 @@ the node name you are connecting to. This only works if the the server
certificate is issued to the name
[`atom_to_list(TargetNode)`](`atom_to_list/1`).

For the server it is also possible to use the option `{verify, verify_peer}` and
For the server it is also possible, and recommended, to use the option `{verify, verify_peer}` and
the server will only accept client connections with certificates that are
trusted by a root certificate that the server knows. A client that presents an
untrusted certificate will be rejected. This option is preferably combined with
`{fail_if_no_peer_cert, true}` or a client will still be accepted if it does not
present any certificate.

> #### Note {: .note }
net_kernel:allow/1 node restrictions rely on verifying the peer
certificate. To enforce them, the server must set `{verify,
verify_peer}` and `{fail_if_no_peer_cert, true}` (default when
verify_peer is set). Without these options, any node can connect
regardless of the allowed list.

A node started in this way is fully functional, using TLS as the distribution
protocol.

Expand Down
29 changes: 29 additions & 0 deletions lib/ssl/doc/guides/ssl_hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,35 @@ avoid possible [DoS-attacks](https://en.wikipedia.org/wiki/Denial-of-service_att
By default, the server mitigates renegotiation abuse by enforcing a
12-second delay between client initiated renegotiations.

### TLS 1.2 Session Resumption and Client Certificates

> #### Warning {: .warning }
> TLS 1.2 session resumption (session ID and RFC 5077 tickets) does
> not bind the master secret to the handshake transcript. When client
> certificate authentication is required, this leaves the connection
> vulnerable to the Triple Handshake attack (see RFC 7627). This
> concern does not apply to TLS 1.3, which binds all session keys to
> the full transcript.

If your TLS 1.2 server uses {verify, verify_peer} to require client
certificates make sure session resumption is disabled:

```erlang
{reuse_sessions, false}
```
This eliminates the Triple Handshake attack surface at the cost of a
full handshake for every connection. For deployments that need both
mutual authentication and session resumption over TLS 1.2, upgrading
to TLS 1.3 is the recommended solution — TLS 1.3 session tickets are
inherently safe.

Note that this concern only applies to the server role. A client
setting `verify_peer` to verify the server is not affected.

The default value for `reuse_sessions` in above described configuration
is false since OTP @OTP-20289@


### Key Exchange Groups
TLS-1.3 decouples key exchange algorithms from cipher suites. The key
exchange algorithms are configured using the
Expand Down
97 changes: 76 additions & 21 deletions lib/ssl/doc/guides/standards_compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,27 @@ Not yet supported

## TLS 1.3

OTP-22 introduces support for TLS 1.3. The current implementation supports a
selective set of cryptographic algorithms:

- Key Exchange: ECDHE groups supported by default
- Groups: all standard groups supported for the Diffie-Hellman key exchange
- Groups: Support brainpool groups from RFC 8734
- Ciphers: all mandatory cipher suites are supported
- Signature Algorithms: All algorithms form RFC 8446
- Certificates: RSA, ECDSA and EDDSA keys

Other notable features:

- PSK and session resumption is supported (stateful and stateless tickets)
- Anti-replay protection using Bloom-filters with stateless tickets
- Early data and 0-RTT is supported
- Key and Initialization Vector Update is supported
TLS 1.3 support was first introduced in OTP 22. The "Since" column in the
table below indicates in which OTP release a feature was first implemented.
We always recommend running the latest patch level of any given release, as
compliance bugs may have been fixed in subsequent patch releases.

The following features from [RFC 8446](https://tools.ietf.org/html/rfc8446)
(or mentioned by it) are **not yet implemented**:

- PSK-only key exchange (without (EC)DHE)
- Post-Handshake Client Authentication (Section 4.6.2)
- OID Filters extension (Section 4.2.5)
- Record padding (sending, Section 5.4)
- Server-side OCSP stapling (status_request in Certificate, Section 4.4.2)
- Supported groups in Encrypted Extensions (Section 4.3.1)
- Heartbeat extension (RFC 6520)
- Signed Certificate Timestamp extension (RFC 6962)
- Raw Public Keys / client_certificate_type and server_certificate_type (RFC 7250)
- Padding extension (RFC 7685)

For more detailed information see the
[Standards Compliance](standards_compliance.md#soc_table) below.

The following table describes the current state of standards compliance for TLS
1.3.
[Standards Compliance](standards_compliance.md#soc_table) table below.

(_C_ = Compliant, _NC_ = Non-Compliant, _PC_ = Partially-Compliant, _NA_ = Not
Applicable)
Expand Down Expand Up @@ -430,8 +429,64 @@ Applicable)
| [C.5. Unauthenticated Operation](https://tools.ietf.org/html/rfc8446#section-C.5) | | C | 22 |
| [D.1. Negotiating with an Older Server](https://tools.ietf.org/html/rfc8446#section-D.1) | | C | 22\.2 |
| [D.2. Negotiating with an Older Client](https://tools.ietf.org/html/rfc8446#section-D.2) | | C | 22 |
| [D.3. 0-RTT Backward Compatibility](https://tools.ietf.org/html/rfc8446#section-D.3) | | NC | |
| [D.3. 0-RTT Backward Compatibility](https://tools.ietf.org/html/rfc8446#section-D.3) | | NA | |
| [D.4. Middlebox Compatibility Mode](https://tools.ietf.org/html/rfc8446#section-D.4) | | C | 23 |
| [D.5. Security Restrictions Related to Backward Compatibility](https://tools.ietf.org/html/rfc8446#section-D.5) | | C | 22 |

_Table: Standards Compliance_

## Post-Quantum Cryptography (PQC)

Post-quantum cryptography support was first introduced in OTP 28. PQC
algorithms are only available with TLS 1.3.

### Key Exchange (ML-KEM)

Hybrid key exchange groups combining ML-KEM (FIPS 203) with classical
ECDHE, as specified in [RFC 10024](https://www.rfc-editor.org/rfc/rfc10024.txt):

| Group | Status | Since |
|-------|--------|-------|
| x25519mlkem768 | Default | 28.3 (default since 29.0) |
| secp256r1mlkem768 | Supported | 28.3 |
| secp384r1mlkem1024 | Supported | 28.3 |

Plain ML-KEM groups (without classical hybrid):

| Group | Status | Since |
|-------|--------|-------|
| mlkem768 | Supported | 28.0 |
| mlkem1024 | Supported | 28.0 |
| mlkem512 | Supported | 28.0 |

### Signature Algorithms

ML-DSA (FIPS 204) as specified in
[draft-ietf-tls-mldsa](https://www.ietf.org/archive/id/draft-ietf-tls-mldsa-01.html):

| Algorithm | Status | Since |
|-----------|--------|-------|
| mldsa44 | Supported | 28.0 |
| mldsa65 | Supported | 28.0 |
| mldsa87 | Supported | 28.0 |

SLH-DSA (FIPS 205):

| Algorithm | Status | Since |
|-----------|--------|-------|
| slh_dsa_sha2_128s | Supported | 28.3 |
| slh_dsa_sha2_128f | Supported | 28.3 |
| slh_dsa_sha2_192s | Supported | 28.3 |
| slh_dsa_sha2_192f | Supported | 28.3 |
| slh_dsa_sha2_256s | Supported | 28.3 |
| slh_dsa_sha2_256f | Supported | 28.3 |
| slh_dsa_shake_128s | Supported | 28.3 |
| slh_dsa_shake_128f | Supported | 28.3 |
| slh_dsa_shake_192s | Supported | 28.3 |
| slh_dsa_shake_192f | Supported | 28.3 |
| slh_dsa_shake_256s | Supported | 28.3 |
| slh_dsa_shake_256f | Supported | 28.3 |

### Not Yet Implemented

- Composite ML-DSA signatures (ML-DSA + RSA/ECDSA in a single certificate)
4 changes: 2 additions & 2 deletions lib/ssl/doc/ssl_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ The environment parameters can be set on the command line, for example:
early_data_indication extension. Defaults to 16384. Size limit is enforced by
both client and server.

- **`client_session_ticket_lifetime = integer() <optional>`** - Lifetime of
- **`client_session_ticket_lifetime = pos_integer() <optional>`** - Lifetime of
session tickets in the client ticket store. Expired tickets are automatically
removed. Defaults to 7200 seconds (2 hours).

- **`client_session_ticket_store_size = integer() <optional>`** - Sets the
- **`client_session_ticket_store_size = pos_integer() <optional>`** - Sets the
maximum size of the client session ticket store. Defaults to 1000. Size limit
is enforced by dropping old tickets.

Expand Down
6 changes: 3 additions & 3 deletions lib/ssl/src/dtls_client_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
-export([callback_mode/0,
terminate/3,
code_change/4,
format_status/2]).
format_status/1]).

%% Tracing
-export([handle_trace/3]).
Expand Down Expand Up @@ -573,8 +573,8 @@ terminate(Reason, StateName, State) ->
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.

format_status(Type, Data) ->
ssl_gen_statem:format_status(Type, Data).
format_status(Data) ->
ssl_gen_statem:format_status(Data).

gen_state(StateName, Type, Event, State) ->
try tls_dtls_client_connection:StateName(Type, Event, State)
Expand Down
8 changes: 5 additions & 3 deletions lib/ssl/src/dtls_gen_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -887,15 +887,17 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{



decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} =
Buffers,
connection_states = ConnStates0} = State) ->
case dtls_record:decode_cipher_text(CT, ConnStates0) of
{Plain, ConnStates} ->
{Plain, ConnStates} ->
{Plain, State#state{protocol_buffers =
Buffers#protocol_buffers{dtls_cipher_texts = Rest},
connection_states = ConnStates}};
#alert{} = Alert ->
{Alert, State}
{Alert, State#state{protocol_buffers =
Buffers#protocol_buffers{dtls_cipher_texts = Rest}}}
end.

decode_alerts(Bin) ->
Expand Down
34 changes: 22 additions & 12 deletions lib/ssl/src/dtls_handshake.erl
Original file line number Diff line number Diff line change
Expand Up @@ -318,24 +318,34 @@ address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
<<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.

%%--------------------------------------------------------------------

handle_fragments(Version, FragmentData, Buffers0, Options, Acc) ->
Fragments = decode_handshake_fragments(FragmentData),
do_handle_fragments(Version, Fragments, Buffers0, Options, Acc).
try decode_handshake_fragments(FragmentData) of
Fragments ->
do_handle_fragments(Version, Fragments, Buffers0, Options, Acc)
catch
error:_Reason ->
throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, malformed_handshake_fragment))
end.

do_handle_fragments(_, [], Buffers, _Options, Acc) ->
{lists:reverse(Acc), Buffers};
do_handle_fragments(Version, [Fragment | Fragments], Buffers0, #{log_level := LogLevel} = Options, Acc) ->
case reassemble(Version, Fragment, Buffers0) of
{more_data, Buffers} when Fragments == [] ->
{lists:reverse(Acc), Buffers};
{more_data, Buffers} ->
do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
{{Handshake, _} = HsPacket, Buffers} ->
do_handle_fragments(Version, [Fragment | Fragments], Buffers0,
#{log_level := LogLevel} = Options, Acc) ->
try reassemble(Version, Fragment, Buffers0) of
{more_data, Buffers} when Fragments == [] ->
{lists:reverse(Acc), Buffers};
{more_data, Buffers} ->
do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
{{Handshake, _} = HsPacket, Buffers} ->
ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake),
do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
catch
error:Reason:ST ->
?SSL_LOG(debug, reassemble_fragment_error, [{reason, Reason}, {stacktrace, ST}]),
throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, malformed_handshake_fragment))
end.


decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
decode_handshake(Version, Type, Bin).

Expand Down Expand Up @@ -379,7 +389,7 @@ decode_tls_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(TLSVersion, Tag, Msg).

decode_handshake_fragments(<<>>) ->
[<<>>];
[];
decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length),
?UINT16(MessageSeq),
?UINT24(FragmentOffset), ?UINT24(FragmentLength),
Expand Down
14 changes: 13 additions & 1 deletion lib/ssl/src/dtls_packet_demux.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
code_change/3,
format_status/1]).

-record(state,
{active_n,
Expand Down Expand Up @@ -279,6 +280,17 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

-spec format_status(map()) -> map().
format_status(Status) ->
maps:map(
fun(state, #state{dtls_options = Options} = State) ->
State#state{dtls_options =
ssl_gen_statem:format_options(Options),
dtls_msq_queues = ?SECRET_PRINTOUT};
(_,Value) ->
Value
end, Status).

%%%===================================================================
%%% Internal functions
%%%===================================================================
Expand Down
17 changes: 12 additions & 5 deletions lib/ssl/src/dtls_record.erl
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ get_connection_state_by_epoch(Epoch, #{current_read := #{epoch := Epoch} = Curre
Current;
get_connection_state_by_epoch(Epoch, #{saved_read := #{epoch := Epoch} = Saved},
read) ->
Saved.
Saved;
%% This can be an attack on read side so return undefined so we can trigger alert
%% on write side this would be a programming error, so let it crash.
get_connection_state_by_epoch(_, _, read) ->
undefined.

set_connection_state_by_epoch(WriteState, Epoch, #{current_write := #{epoch := Epoch}} = States,
write) ->
Expand Down Expand Up @@ -253,10 +257,13 @@ encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) ->
%% Decoding
%%====================================================================

decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
ReadState = get_connection_state_by_epoch(Epoch, ConnnectionStates0, read),
decode_cipher_text(CipherText, ReadState, ConnnectionStates0).

decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnectionStates0) ->
case get_connection_state_by_epoch(Epoch, ConnectionStates0, read) of
undefined ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC);
ReadState ->
decode_cipher_text(CipherText, ReadState, ConnectionStates0)
end.

%%====================================================================
%% Protocol version handling
Expand Down
Loading
Loading