Skip to content

Commit 6e62cd2

Browse files
committed
Merge branch 'maint'
2 parents 12c91e6 + 8c9a51e commit 6e62cd2

43 files changed

Lines changed: 1307 additions & 536 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/ssl/doc/guides/ssl_distribution.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,20 @@ the node name you are connecting to. This only works if the the server
206206
certificate is issued to the name
207207
[`atom_to_list(TargetNode)`](`atom_to_list/1`).
208208

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

216+
> #### Note {: .note }
217+
net_kernel:allow/1 node restrictions rely on verifying the peer
218+
certificate. To enforce them, the server must set `{verify,
219+
verify_peer}` and `{fail_if_no_peer_cert, true}` (default when
220+
verify_peer is set). Without these options, any node can connect
221+
regardless of the allowed list.
222+
216223
A node started in this way is fully functional, using TLS as the distribution
217224
protocol.
218225

lib/ssl/doc/guides/ssl_hardening.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,35 @@ avoid possible [DoS-attacks](https://en.wikipedia.org/wiki/Denial-of-service_att
174174
By default, the server mitigates renegotiation abuse by enforcing a
175175
12-second delay between client initiated renegotiations.
176176

177+
### TLS 1.2 Session Resumption and Client Certificates
178+
179+
> #### Warning {: .warning }
180+
> TLS 1.2 session resumption (session ID and RFC 5077 tickets) does
181+
> not bind the master secret to the handshake transcript. When client
182+
> certificate authentication is required, this leaves the connection
183+
> vulnerable to the Triple Handshake attack (see RFC 7627). This
184+
> concern does not apply to TLS 1.3, which binds all session keys to
185+
> the full transcript.
186+
187+
If your TLS 1.2 server uses {verify, verify_peer} to require client
188+
certificates make sure session resumption is disabled:
189+
190+
```erlang
191+
{reuse_sessions, false}
192+
```
193+
This eliminates the Triple Handshake attack surface at the cost of a
194+
full handshake for every connection. For deployments that need both
195+
mutual authentication and session resumption over TLS 1.2, upgrading
196+
to TLS 1.3 is the recommended solution — TLS 1.3 session tickets are
197+
inherently safe.
198+
199+
Note that this concern only applies to the server role. A client
200+
setting `verify_peer` to verify the server is not affected.
201+
202+
The default value for `reuse_sessions` in above described configuration
203+
is false since OTP @OTP-20289@
204+
205+
177206
### Key Exchange Groups
178207
TLS-1.3 decouples key exchange algorithms from cipher suites. The key
179208
exchange algorithms are configured using the

lib/ssl/doc/guides/standards_compliance.md

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -98,28 +98,27 @@ Not yet supported
9898

9999
## TLS 1.3
100100

101-
OTP-22 introduces support for TLS 1.3. The current implementation supports a
102-
selective set of cryptographic algorithms:
103-
104-
- Key Exchange: ECDHE groups supported by default
105-
- Groups: all standard groups supported for the Diffie-Hellman key exchange
106-
- Groups: Support brainpool groups from RFC 8734
107-
- Ciphers: all mandatory cipher suites are supported
108-
- Signature Algorithms: All algorithms form RFC 8446
109-
- Certificates: RSA, ECDSA and EDDSA keys
110-
111-
Other notable features:
112-
113-
- PSK and session resumption is supported (stateful and stateless tickets)
114-
- Anti-replay protection using Bloom-filters with stateless tickets
115-
- Early data and 0-RTT is supported
116-
- Key and Initialization Vector Update is supported
101+
TLS 1.3 support was first introduced in OTP 22. The "Since" column in the
102+
table below indicates in which OTP release a feature was first implemented.
103+
We always recommend running the latest patch level of any given release, as
104+
compliance bugs may have been fixed in subsequent patch releases.
105+
106+
The following features from [RFC 8446](https://tools.ietf.org/html/rfc8446)
107+
(or mentioned by it) are **not yet implemented**:
108+
109+
- PSK-only key exchange (without (EC)DHE)
110+
- Post-Handshake Client Authentication (Section 4.6.2)
111+
- OID Filters extension (Section 4.2.5)
112+
- Record padding (sending, Section 5.4)
113+
- Server-side OCSP stapling (status_request in Certificate, Section 4.4.2)
114+
- Supported groups in Encrypted Extensions (Section 4.3.1)
115+
- Heartbeat extension (RFC 6520)
116+
- Signed Certificate Timestamp extension (RFC 6962)
117+
- Raw Public Keys / client_certificate_type and server_certificate_type (RFC 7250)
118+
- Padding extension (RFC 7685)
117119

118120
For more detailed information see the
119-
[Standards Compliance](standards_compliance.md#soc_table) below.
120-
121-
The following table describes the current state of standards compliance for TLS
122-
1.3.
121+
[Standards Compliance](standards_compliance.md#soc_table) table below.
123122

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

437436
_Table: Standards Compliance_
437+
438+
## Post-Quantum Cryptography (PQC)
439+
440+
Post-quantum cryptography support was first introduced in OTP 28. PQC
441+
algorithms are only available with TLS 1.3.
442+
443+
### Key Exchange (ML-KEM)
444+
445+
Hybrid key exchange groups combining ML-KEM (FIPS 203) with classical
446+
ECDHE, as specified in [RFC 10024](https://www.rfc-editor.org/rfc/rfc10024.txt):
447+
448+
| Group | Status | Since |
449+
|-------|--------|-------|
450+
| x25519mlkem768 | Default | 28.3 (default since 29.0) |
451+
| secp256r1mlkem768 | Supported | 28.3 |
452+
| secp384r1mlkem1024 | Supported | 28.3 |
453+
454+
Plain ML-KEM groups (without classical hybrid):
455+
456+
| Group | Status | Since |
457+
|-------|--------|-------|
458+
| mlkem768 | Supported | 28.0 |
459+
| mlkem1024 | Supported | 28.0 |
460+
| mlkem512 | Supported | 28.0 |
461+
462+
### Signature Algorithms
463+
464+
ML-DSA (FIPS 204) as specified in
465+
[draft-ietf-tls-mldsa](https://www.ietf.org/archive/id/draft-ietf-tls-mldsa-01.html):
466+
467+
| Algorithm | Status | Since |
468+
|-----------|--------|-------|
469+
| mldsa44 | Supported | 28.0 |
470+
| mldsa65 | Supported | 28.0 |
471+
| mldsa87 | Supported | 28.0 |
472+
473+
SLH-DSA (FIPS 205):
474+
475+
| Algorithm | Status | Since |
476+
|-----------|--------|-------|
477+
| slh_dsa_sha2_128s | Supported | 28.3 |
478+
| slh_dsa_sha2_128f | Supported | 28.3 |
479+
| slh_dsa_sha2_192s | Supported | 28.3 |
480+
| slh_dsa_sha2_192f | Supported | 28.3 |
481+
| slh_dsa_sha2_256s | Supported | 28.3 |
482+
| slh_dsa_sha2_256f | Supported | 28.3 |
483+
| slh_dsa_shake_128s | Supported | 28.3 |
484+
| slh_dsa_shake_128f | Supported | 28.3 |
485+
| slh_dsa_shake_192s | Supported | 28.3 |
486+
| slh_dsa_shake_192f | Supported | 28.3 |
487+
| slh_dsa_shake_256s | Supported | 28.3 |
488+
| slh_dsa_shake_256f | Supported | 28.3 |
489+
490+
### Not Yet Implemented
491+
492+
- Composite ML-DSA signatures (ML-DSA + RSA/ECDSA in a single certificate)

lib/ssl/doc/ssl_app.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ The environment parameters can be set on the command line, for example:
148148
early_data_indication extension. Defaults to 16384. Size limit is enforced by
149149
both client and server.
150150

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

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

lib/ssl/src/dtls_client_connection.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
-export([callback_mode/0,
148148
terminate/3,
149149
code_change/4,
150-
format_status/2]).
150+
format_status/1]).
151151

152152
%% Tracing
153153
-export([handle_trace/3]).
@@ -573,8 +573,8 @@ terminate(Reason, StateName, State) ->
573573
code_change(_OldVsn, StateName, State, _Extra) ->
574574
{ok, StateName, State}.
575575

576-
format_status(Type, Data) ->
577-
ssl_gen_statem:format_status(Type, Data).
576+
format_status(Data) ->
577+
ssl_gen_statem:format_status(Data).
578578

579579
gen_state(StateName, Type, Event, State) ->
580580
try tls_dtls_client_connection:StateName(Type, Event, State)

lib/ssl/src/dtls_gen_connection.erl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,15 +887,17 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
887887

888888

889889

890-
decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
890+
decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} =
891+
Buffers,
891892
connection_states = ConnStates0} = State) ->
892893
case dtls_record:decode_cipher_text(CT, ConnStates0) of
893-
{Plain, ConnStates} ->
894+
{Plain, ConnStates} ->
894895
{Plain, State#state{protocol_buffers =
895896
Buffers#protocol_buffers{dtls_cipher_texts = Rest},
896897
connection_states = ConnStates}};
897898
#alert{} = Alert ->
898-
{Alert, State}
899+
{Alert, State#state{protocol_buffers =
900+
Buffers#protocol_buffers{dtls_cipher_texts = Rest}}}
899901
end.
900902

901903
decode_alerts(Bin) ->

lib/ssl/src/dtls_handshake.erl

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -318,24 +318,34 @@ address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
318318
<<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.
319319

320320
%%--------------------------------------------------------------------
321-
322321
handle_fragments(Version, FragmentData, Buffers0, Options, Acc) ->
323-
Fragments = decode_handshake_fragments(FragmentData),
324-
do_handle_fragments(Version, Fragments, Buffers0, Options, Acc).
322+
try decode_handshake_fragments(FragmentData) of
323+
Fragments ->
324+
do_handle_fragments(Version, Fragments, Buffers0, Options, Acc)
325+
catch
326+
error:_Reason ->
327+
throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, malformed_handshake_fragment))
328+
end.
325329

326330
do_handle_fragments(_, [], Buffers, _Options, Acc) ->
327331
{lists:reverse(Acc), Buffers};
328-
do_handle_fragments(Version, [Fragment | Fragments], Buffers0, #{log_level := LogLevel} = Options, Acc) ->
329-
case reassemble(Version, Fragment, Buffers0) of
330-
{more_data, Buffers} when Fragments == [] ->
331-
{lists:reverse(Acc), Buffers};
332-
{more_data, Buffers} ->
333-
do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
334-
{{Handshake, _} = HsPacket, Buffers} ->
332+
do_handle_fragments(Version, [Fragment | Fragments], Buffers0,
333+
#{log_level := LogLevel} = Options, Acc) ->
334+
try reassemble(Version, Fragment, Buffers0) of
335+
{more_data, Buffers} when Fragments == [] ->
336+
{lists:reverse(Acc), Buffers};
337+
{more_data, Buffers} ->
338+
do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
339+
{{Handshake, _} = HsPacket, Buffers} ->
335340
ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake),
336-
do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
341+
do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
342+
catch
343+
error:Reason:ST ->
344+
?SSL_LOG(debug, reassemble_fragment_error, [{reason, Reason}, {stacktrace, ST}]),
345+
throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, malformed_handshake_fragment))
337346
end.
338347

348+
339349
decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
340350
decode_handshake(Version, Type, Bin).
341351

@@ -379,7 +389,7 @@ decode_tls_handshake(Version, Tag, Msg) ->
379389
ssl_handshake:decode_handshake(TLSVersion, Tag, Msg).
380390

381391
decode_handshake_fragments(<<>>) ->
382-
[<<>>];
392+
[];
383393
decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length),
384394
?UINT16(MessageSeq),
385395
?UINT24(FragmentOffset), ?UINT24(FragmentLength),

lib/ssl/src/dtls_packet_demux.erl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
handle_cast/2,
5252
handle_info/2,
5353
terminate/2,
54-
code_change/3]).
54+
code_change/3,
55+
format_status/1]).
5556

5657
-record(state,
5758
{active_n,
@@ -279,6 +280,17 @@ terminate(_Reason, _State) ->
279280
code_change(_OldVsn, State, _Extra) ->
280281
{ok, State}.
281282

283+
-spec format_status(map()) -> map().
284+
format_status(Status) ->
285+
maps:map(
286+
fun(state, #state{dtls_options = Options} = State) ->
287+
State#state{dtls_options =
288+
ssl_gen_statem:format_options(Options),
289+
dtls_msq_queues = ?SECRET_PRINTOUT};
290+
(_,Value) ->
291+
Value
292+
end, Status).
293+
282294
%%%===================================================================
283295
%%% Internal functions
284296
%%%===================================================================

lib/ssl/src/dtls_record.erl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ get_connection_state_by_epoch(Epoch, #{current_read := #{epoch := Epoch} = Curre
128128
Current;
129129
get_connection_state_by_epoch(Epoch, #{saved_read := #{epoch := Epoch} = Saved},
130130
read) ->
131-
Saved.
131+
Saved;
132+
%% This can be an attack on read side so return undefined so we can trigger alert
133+
%% on write side this would be a programming error, so let it crash.
134+
get_connection_state_by_epoch(_, _, read) ->
135+
undefined.
132136

133137
set_connection_state_by_epoch(WriteState, Epoch, #{current_write := #{epoch := Epoch}} = States,
134138
write) ->
@@ -253,10 +257,13 @@ encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) ->
253257
%% Decoding
254258
%%====================================================================
255259

256-
decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
257-
ReadState = get_connection_state_by_epoch(Epoch, ConnnectionStates0, read),
258-
decode_cipher_text(CipherText, ReadState, ConnnectionStates0).
259-
260+
decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnectionStates0) ->
261+
case get_connection_state_by_epoch(Epoch, ConnectionStates0, read) of
262+
undefined ->
263+
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC);
264+
ReadState ->
265+
decode_cipher_text(CipherText, ReadState, ConnectionStates0)
266+
end.
260267

261268
%%====================================================================
262269
%% Protocol version handling

0 commit comments

Comments
 (0)