aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/dtls_connection.erl9
-rw-r--r--lib/ssl/src/dtls_handshake.erl3
-rw-r--r--lib/ssl/src/dtls_record.erl87
-rw-r--r--lib/ssl/src/ssl.erl14
-rw-r--r--lib/ssl/src/ssl_cipher.erl48
-rw-r--r--lib/ssl/src/ssl_connection.erl70
-rw-r--r--lib/ssl/src/ssl_connection.hrl3
-rw-r--r--lib/ssl/src/ssl_handshake.erl47
-rw-r--r--lib/ssl/src/ssl_logger.erl5
-rw-r--r--lib/ssl/src/tls_connection.erl22
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl61
-rw-r--r--lib/ssl/src/tls_handshake.erl43
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl838
-rw-r--r--lib/ssl/src/tls_record.erl23
14 files changed, 1004 insertions, 269 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 6928d7a93d..b220691e79 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -836,9 +836,12 @@ initial_flight_state(_) ->
next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
dtls_record_buffer = Buf0,
dtls_cipher_texts = CT0} = Buffers,
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{data_tag = DataTag},
ssl_options = SslOpts} = State0) ->
case dtls_record:get_dtls_records(Data,
- acceptable_record_versions(StateName, State0),
+ {DataTag, StateName, Version,
+ [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
@@ -849,10 +852,6 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
Alert
end.
-acceptable_record_versions(hello, _) ->
- [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS];
-acceptable_record_versions(_, #state{connection_env = #connection_env{negotiated_version = Version}}) ->
- [Version].
dtls_handshake_events(Packets) ->
lists:map(fun(Packet) ->
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index d8c0e30973..4a381745d4 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -255,7 +255,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions,
+ dtls_v1:corresponding_tls_version({Major, Minor})),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index a4846f42c5..8b8db7b2de 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -162,26 +162,16 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
Epoch.
%%--------------------------------------------------------------------
--spec get_dtls_records(binary(), [ssl_record:ssl_version()], binary(),
+-spec get_dtls_records(binary(), {atom(), atom(), ssl_record:ssl_version(), [ssl_record:ssl_version()]}, binary(),
#ssl_options{}) -> {[binary()], binary()} | #alert{}.
%%
%% Description: Given old buffer and new data from UDP/SCTP, packs up a records
%% and returns it as a list of tls_compressed binaries also returns leftover
%% data
%%--------------------------------------------------------------------
-get_dtls_records(Data, Versions, Buffer, SslOpts) ->
+get_dtls_records(Data, Vinfo, Buffer, SslOpts) ->
BinData = list_to_binary([Buffer, Data]),
- case erlang:byte_size(BinData) of
- N when N >= 3 ->
- case assert_version(BinData, Versions) of
- true ->
- get_dtls_records_aux(BinData, [], SslOpts);
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- _ ->
- get_dtls_records_aux(BinData, [], SslOpts)
- end.
+ get_dtls_records_aux(Vinfo, BinData, [], SslOpts).
%%====================================================================
%% Encoding DTLS records
@@ -405,52 +395,49 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
client_verify_data => undefined,
server_verify_data => undefined
}.
-assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) ->
- is_acceptable_version({MajVer, MinVer}, Versions).
-get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
- Acc, SslOpts) ->
- ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
-get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length),
- Data:Length/binary, Rest/binary>> = RawDTLSRecord,
- Acc, SslOpts) when MajVer >= 128 ->
- ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
-get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary,
- Rest/binary>> = RawDTLSRecord, Acc, SslOpts) ->
+get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Epoch), ?UINT48(SequenceNumber),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) when ((StateName == hello) orelse
+ ((StateName == certify) andalso (DataTag == udp)) orelse
+ ((StateName == abbreviated) andalso(DataTag == udp)))
+ andalso
+ ((Type == ?HANDSHAKE) orelse
+ (Type == ?ALERT)) ->
ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
-get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
+ case is_acceptable_version({MajVer, MinVer}, Versions) of
+ true ->
+ get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc], SslOpts);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_dtls_records_aux({_, _, Version, _} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Epoch), ?UINT48(SequenceNumber),
?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
- Acc, SslOpts) ->
+ Acc, SslOpts) when (Type == ?APPLICATION_DATA) orelse
+ (Type == ?HANDSHAKE) orelse
+ (Type == ?ALERT) orelse
+ (Type == ?CHANGE_CIPHER_SPEC) ->
ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
- get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {MajVer, MinVer},
- epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc], SslOpts);
-get_dtls_records_aux(<<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer),
+ case {MajVer, MinVer} of
+ Version ->
+ get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ epoch = Epoch, sequence_number = SequenceNumber,
+ fragment = Data} | Acc], SslOpts);
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_dtls_records_aux(_, <<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer),
?UINT16(Length), _/binary>>,
_Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-get_dtls_records_aux(Data, Acc, _) ->
+get_dtls_records_aux(_, Data, Acc, _) ->
case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
true ->
{lists:reverse(Acc), Data};
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 6af65e09f2..20b1e85ceb 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -125,7 +125,10 @@
protocol_extensions/0,
session_id/0,
error_alert/0,
- srp_param_type/0]).
+ tls_alert/0,
+ srp_param_type/0,
+ named_curve/0,
+ sign_scheme/0]).
%% -------------------------------------------------------------------------------------------------------
@@ -191,7 +194,8 @@
| rsa_pss_pss_sha384
| rsa_pss_pss_sha512
| rsa_pkcs1_sha1
- | ecdsa_sha1.
+ | ecdsa_sha1. % exported
+
-type kex_algo() :: rsa |
dhe_rsa | dhe_dss |
ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa |
@@ -236,7 +240,7 @@
sect163r2 |
secp160k1 |
secp160r1 |
- secp160r2.
+ secp160r2. % exported
-type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192.
@@ -279,7 +283,7 @@
bad_certificate_status_response |
bad_certificate_hash_value |
unknown_psk_identity |
- no_application_protocol.
+ no_application_protocol. % exported
%% -------------------------------------------------------------------------------------------------------
-type common_option() :: {protocol, protocol()} |
@@ -1909,7 +1913,7 @@ validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) ->
- {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "passive")};
+ {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")};
handle_cb_info(CbInfo, _) ->
CbInfo.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 4da50d2af8..c16e2331ff 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -38,7 +38,7 @@
cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6,
suites/1, all_suites/1, crypto_support_filters/0,
chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
- srp_suites/0, srp_suites_anon/0,
+ srp_suites/1, srp_suites_anon/1,
rc4_suites/1, des_suites/1, rsa_suites/1,
filter/3, filter_suites/1, filter_suites/2,
hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
@@ -284,7 +284,7 @@ all_suites({3, _} = Version) ->
suites(Version)
++ chacha_suites(Version)
++ psk_suites(Version)
- ++ srp_suites()
+ ++ srp_suites(Version)
++ rc4_suites(Version)
++ des_suites(Version)
++ rsa_suites(Version);
@@ -313,8 +313,8 @@ chacha_suites(_) ->
%% Description: Returns a list of the anonymous cipher suites, only supported
%% if explicitly set by user. Intended only for testing.
%%--------------------------------------------------------------------
-anonymous_suites({3, N}) ->
- srp_suites_anon() ++ anonymous_suites(N);
+anonymous_suites({3, N} = Version) ->
+ srp_suites_anon(Version) ++ anonymous_suites(N);
anonymous_suites({254, _} = Version) ->
dtls_v1:anonymous_suites(Version);
anonymous_suites(4) ->
@@ -375,7 +375,7 @@ psk_suites(_) ->
%%--------------------------------------------------------------------
psk_suites_anon({3, N}) ->
psk_suites_anon(N);
-psk_suites_anon(3) ->
+psk_suites_anon(3 = N) ->
[
?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
?TLS_PSK_WITH_AES_256_GCM_SHA384,
@@ -401,8 +401,8 @@ psk_suites_anon(3) ->
?TLS_PSK_WITH_AES_128_CCM,
?TLS_PSK_WITH_AES_128_CCM_8,
?TLS_ECDHE_PSK_WITH_RC4_128_SHA
- ] ++ psk_suites_anon(0);
-psk_suites_anon(_) ->
+ ] ++ psk_suites_anon(N-1);
+psk_suites_anon(N) when N > 0 ->
[?TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
?TLS_PSK_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
@@ -413,14 +413,18 @@ psk_suites_anon(_) ->
?TLS_PSK_WITH_3DES_EDE_CBC_SHA,
?TLS_ECDHE_PSK_WITH_RC4_128_SHA,
?TLS_DHE_PSK_WITH_RC4_128_SHA,
- ?TLS_PSK_WITH_RC4_128_SHA].
+ ?TLS_PSK_WITH_RC4_128_SHA];
+psk_suites_anon(0) ->
+ [].
%%--------------------------------------------------------------------
--spec srp_suites() -> [ssl_cipher_format:cipher_suite()].
+-spec srp_suites(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the SRP cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites() ->
+srp_suites({3,0}) ->
+ [];
+srp_suites(_) ->
[?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
@@ -429,12 +433,14 @@ srp_suites() ->
?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA].
%%--------------------------------------------------------------------
--spec srp_suites_anon() -> [ssl_cipher_format:cipher_suite()].
+-spec srp_suites_anon(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the SRP anonymous cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites_anon() ->
+srp_suites_anon({3,0}) ->
+ [];
+srp_suites_anon(_) ->
[?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
?TLS_SRP_SHA_WITH_AES_256_CBC_SHA].
@@ -973,15 +979,25 @@ scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined};
scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
-%% TODO: Add support for EC and RSA-SSA signatures
-signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
- rsa_pkcs1_sha1;
+%% TODO: Add support for ed25519, ed448, rsa_pss*
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) ->
rsa_pkcs1_sha256;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) ->
rsa_pkcs1_sha384;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha512WithRSAEncryption}) ->
- rsa_pkcs1_sha512.
+ rsa_pkcs1_sha512;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA256'}) ->
+ ecdsa_secp256r1_sha256;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA384'}) ->
+ ecdsa_secp384r1_sha384;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA512'}) ->
+ ecdsa_secp512r1_sha512;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEncryption'}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) ->
+ ecdsa_sha1.
%% RFC 5246: 6.2.3.2. CBC Block Cipher
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index a8cb9ea815..2483509228 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -614,7 +614,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
<<SizeA:32, DataA:SizeA/binary,
SizeB:32, DataB:SizeB/binary,
SizeC:32, DataC:SizeC/binary,
- SizeD:32, DataD:SizeD/binary, Rest/binary>> ->
+ SizeD:32, DataD:SizeD/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
%% We have 4 complete packets in the first binary
erlang:dist_ctrl_put_data(DHandle, DataA),
erlang:dist_ctrl_put_data(DHandle, DataB),
@@ -624,7 +625,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
<<SizeA:32, DataA:SizeA/binary,
SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary, Rest/binary>> ->
+ SizeC:32, DataC:SizeC/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
%% We have 3 complete packets in the first binary
erlang:dist_ctrl_put_data(DHandle, DataA),
erlang:dist_ctrl_put_data(DHandle, DataB),
@@ -632,7 +634,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
read_application_dist_data(
DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
<<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary, Rest/binary>> ->
+ SizeB:32, DataB:SizeB/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB ->
%% We have 2 complete packets in the first binary
erlang:dist_ctrl_put_data(DHandle, DataA),
erlang:dist_ctrl_put_data(DHandle, DataB),
@@ -643,13 +646,13 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
%% Basic one packet code path
<<Size:32, Data:Size/binary, Rest/binary>> ->
%% We have a complete packet in the first binary
- erlang:dist_ctrl_put_data(DHandle, Data),
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
<<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
%% We have a complete packet in the buffer
%% - fetch the missing content from the buffer front
{Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
- erlang:dist_ctrl_put_data(DHandle, Data),
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
<<Bin/binary>> ->
%% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
@@ -665,23 +668,61 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
%% contains enough data to maybe form a packet
%% - fetch a tiny binary from the buffer front to complete the length field
{LengthField,Front,Rear} =
- iovec_from_front(4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]),
+ case IncompleteLengthField of
+ <<>> ->
+ iovec_from_front(4, Front0, Rear0, []);
+ _ ->
+ iovec_from_front(
+ 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
+ end,
LengthBin = iolist_to_binary(LengthField),
read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
<<IncompleteLengthField/binary>> ->
%% We do not have enough data in the buffer to even form a length field - await more data
- {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ case IncompleteLengthField of
+ <<>> ->
+ {Front0,BufferSize,Rear0};
+ _ ->
+ {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ end
end
end.
+iovec_from_front(0, Front, Rear, Acc) ->
+ {lists:reverse(Acc),Front,Rear};
iovec_from_front(Size, [], Rear, Acc) ->
- iovec_from_front(Size, lists:reverse(Rear), [], Acc);
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ iovec_from_front(Size, Rear, [], Acc);
+ [Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2], [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
+ [_,_,_|_] = Rear ->
+ iovec_from_front(Size, lists:reverse(Rear), [], Acc)
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, []) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {[Last],Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {[Last],[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, []);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin])
+ end;
iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
case Bin of
<<Last:Size/binary>> -> % Just enough
{lists:reverse(Acc, [Last]),Front,Rear};
<<Last:Size/binary, Rest/binary>> -> % More than enough, split here
{lists:reverse(Acc, [Last]),[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, Acc);
<<_/binary>> -> % Not enough
BinSize = byte_size(Bin),
iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
@@ -1195,7 +1236,7 @@ cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{expecting_finished = true,
expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
+ Connection:next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
expecting_next_protocol_negotiation = false}});
cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} =
@@ -1234,10 +1275,17 @@ connection({call, From}, {connection_information, false}, State, _) ->
Info = connection_info(State),
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{negotiated_protocol = SelectedProtocol}} = State, _) ->
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State, _) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, Msg, State, Connection) ->
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index ff7207a8ce..844368c761 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -66,6 +66,7 @@
sni_hostname = undefined,
expecting_next_protocol_negotiation = false ::boolean(),
next_protocol = undefined :: undefined | binary(),
+ alpn = undefined, %% Used in TLS 1.3
negotiated_protocol,
hashsign_algorithm = {undefined, undefined},
cert_hashsign_algorithm = {undefined, undefined},
@@ -76,7 +77,7 @@
srp_params :: #srp_user{} | secret_printout() | 'undefined',
public_key_info :: ssl_handshake:public_key_info() | 'undefined',
premaster_secret :: binary() | secret_printout() | 'undefined',
- server_psk_identity :: binary() | 'undefined' % server psk identity hint
+ server_psk_identity :: binary() | 'undefined' % server psk identity hint
}).
-record(connection_env, {
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index fbed7258c6..488e4bb72a 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -58,7 +58,7 @@
]).
%% Encode
--export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2,
+-export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2,
encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
%% Decode
-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
@@ -76,7 +76,8 @@
handle_client_hello_extensions/9, %% Returns server hello extensions
handle_server_hello_extensions/9, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
- select_hashsign_algs/3, empty_extensions/2, add_server_share/3
+ select_hashsign_algs/3, empty_extensions/2, add_server_share/3,
+ add_alpn/2, add_selected_version/1, decode_alpn/1
]).
-export([get_cert_params/1,
@@ -533,14 +534,14 @@ encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version)
PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32),
{?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary,
?BYTE(PaddingLength), 0:(PaddingLength * 8)>>};
-encode_handshake(#server_hello{server_version = {Major, Minor},
+encode_handshake(#server_hello{server_version = {Major, Minor} = Version,
random = Random,
session_id = Session_ID,
cipher_suite = CipherSuite,
compression_method = Comp_method,
extensions = Extensions}, _Version) ->
SID_length = byte_size(Session_ID),
- ExtensionsBin = encode_hello_extensions(Extensions),
+ ExtensionsBin = encode_hello_extensions(Extensions, Version),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>};
@@ -588,7 +589,9 @@ encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = Ha
encode_handshake(#finished{verify_data = VerifyData}, _Version) ->
{?FINISHED, VerifyData}.
-encode_hello_extensions(Extensions) ->
+encode_hello_extensions(_, {3, 0}) ->
+ <<>>;
+encode_hello_extensions(Extensions, _) ->
encode_extensions(hello_extensions_list(Extensions), <<>>).
encode_extensions(Exts) ->
@@ -1165,6 +1168,13 @@ add_server_share(hello_retry_request, Extensions,
Extensions#{key_share => #key_share_hello_retry_request{
selected_group = Group}}.
+add_alpn(Extensions, ALPN0) ->
+ ALPN = encode_alpn([ALPN0], false),
+ Extensions#{alpn => ALPN}.
+
+add_selected_version(Extensions) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions#{server_hello_selected_version => SupportedVersions}.
kse_remove_private_key(#key_share_entry{
group = Group,
@@ -1248,6 +1258,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
%% We also ignore the ALPN extension during renegotiation (see encode_alpn/2).
[Protocol] when not Renegotiation ->
{ConnectionStates, alpn, Protocol};
+ [_] when Renegotiation ->
+ {ConnectionStates, alpn, undefined};
undefined ->
NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
@@ -2658,7 +2670,7 @@ filter_unavailable_ecc_suites(_, Suites) ->
handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation) ->
- {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
+ {ok, ConnectionStates} = handle_renegotiation_info(Version, RecordCB, Role, Info, ConnectionStates0,
Renegotiation, SecureRenegotation,
ClientCipherSuites),
hello_pending_connection_states(RecordCB, Role,
@@ -2928,11 +2940,11 @@ renegotiation_info(_RecordCB, server, ConnectionStates, true) ->
#renegotiation_info{renegotiated_connection = undefined}
end.
-handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)},
+handle_renegotiation_info(_, _RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)},
ConnectionStates, false, _, _) ->
{ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
-handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) ->
+handle_renegotiation_info(_, _RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
{ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
@@ -2940,10 +2952,10 @@ handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _,
{ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}
end;
-handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) ->
+handle_renegotiation_info(_, _RecordCB, _, undefined, ConnectionStates, false, _, _) ->
{ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)};
-handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify},
+handle_renegotiation_info(_, _RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify},
ConnectionStates, true, _, _) ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
CData = maps:get(client_verify_data, ConnectionState),
@@ -2954,7 +2966,7 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co
false ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation))
end;
-handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
+handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
ConnectionStates, true, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
@@ -2970,11 +2982,13 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation))
end
end;
+handle_renegotiation_info({3,0}, _RecordCB, client, undefined, ConnectionStates, true, _SecureRenegotation, _) ->
+ {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
-handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) ->
+handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation);
-handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
+handle_renegotiation_info(_, RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
@@ -3052,6 +3066,13 @@ empty_extensions({3,4}, server_hello) ->
key_share => undefined,
pre_shared_key => undefined
};
+empty_extensions({3,4}, hello_retry_request) ->
+ #{server_hello_selected_version => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined
+ };
+empty_extensions({3,0}, _) ->
+ empty_extensions();
empty_extensions(_, server_hello) ->
#{renegotiation_info => undefined,
alpn => undefined,
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 987693b96b..514a4464bc 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -200,6 +200,11 @@ parse_handshake(Direction, #encrypted_extensions{} = EncryptedExtensions) ->
Header = io_lib:format("~s Handshake, EncryptedExtensions",
[header_prefix(Direction)]),
Message = io_lib:format("~p", [?rec_info(encrypted_extensions, EncryptedExtensions)]),
+ {Header, Message};
+parse_handshake(Direction, #new_session_ticket{} = NewSessionTicket) ->
+ Header = io_lib:format("~s Post-Handshake, NewSessionTicket",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(new_session_ticket, NewSessionTicket)]),
{Header, Message}.
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index f317cb169e..3998f03519 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -31,6 +31,7 @@
-include("tls_connection.hrl").
-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
-include("ssl_alert.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
@@ -395,6 +396,7 @@ queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_h
handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
+
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
flight_buffer = Flight} = State0) ->
@@ -660,10 +662,16 @@ hello(internal, #server_hello{} = Hello,
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert -> %%TODO
ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
- State#state{connection_env = CEnv#connection_env{negotiated_version = ReqVersion}});
+ State#state{connection_env =
+ CEnv#connection_env{negotiated_version = ReqVersion}});
+ %% Legacy TLS 1.2 and older
{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
ssl_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol, State)
+ Version, NewId, ConnectionStates, ProtoExt, Protocol, State);
+ %% TLS 1.3
+ {next_state, wait_sh} ->
+ %% Continue in TLS 1.3 'wait_sh' state
+ {next_state, wait_sh, State, [{next_event, internal, Hello}]}
end;
hello(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
@@ -804,6 +812,11 @@ connection(internal, #client_hello{},
State = reinit_handshake_data(State0),
next_event(?FUNCTION_NAME, no_record, State);
+connection(internal, #new_session_ticket{}, State) ->
+ %% TLS 1.3
+ %% Drop NewSessionTicket (currently not supported)
+ next_event(?FUNCTION_NAME, no_record, State);
+
connection(Type, Event, State) ->
ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
@@ -1286,9 +1299,10 @@ maybe_generate_client_shares(#ssl_options{
versions = [Version|_],
supported_groups =
#supported_groups{
- supported_groups = Groups}})
+ supported_groups = [Group|_]}})
when Version =:= {3,4} ->
- ssl_cipher:generate_client_shares(Groups);
+ %% Generate only key_share entry for the most preferred group
+ ssl_cipher:generate_client_shares([Group]);
maybe_generate_client_shares(_) ->
undefined.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 76cdebc76f..117e4f059d 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -112,7 +112,10 @@
negotiated/4,
wait_cert/4,
wait_cv/4,
- wait_finished/4
+ wait_finished/4,
+ wait_sh/4,
+ wait_ee/4,
+ wait_cert_cr/4
]).
@@ -127,6 +130,13 @@ start(internal, #client_hello{} = Hello, State0, _Module) ->
{State, negotiated} ->
{next_state, negotiated, State, [{next_event, internal, start_handshake}]}
end;
+start(internal, #server_hello{} = ServerHello, State0, _Module) ->
+ case tls_handshake_1_3:do_start(ServerHello, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end;
start(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -183,3 +193,52 @@ wait_finished(internal,
end;
wait_finished(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+wait_sh(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_sh(internal, #server_hello{} = Hello, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_sh(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0);
+ {State1, start, ServerHello} ->
+ %% hello_retry_request: go to start
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} ->
+ tls_connection:next_event(wait_ee, no_record, State1)
+ end;
+wait_sh(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+wait_ee(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_ee(EE, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0);
+ {State1, NextState} ->
+ tls_connection:next_event(NextState, no_record, State1)
+ end;
+wait_ee(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+wait_cert_cr(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
+ {State1, NextState} ->
+ tls_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
+ {State1, NextState} ->
+ tls_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 2480e05097..37265e0759 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -105,7 +105,7 @@ client_hello(Host, Port, ConnectionStates,
{tls_record:tls_version(), {resumed | new, #session{}},
ssl_record:connection_states(), binary() | undefined,
HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
- undefined} | #alert{}.
+ undefined} | {atom(), atom()} |#alert{}.
%%
%% Description: Handles a received hello message
%%--------------------------------------------------------------------
@@ -148,29 +148,48 @@ hello(#server_hello{server_version = {Major, Minor},
%%
%% - If "supported_version" is present (ServerHello):
%% - Abort handshake with an "illegal_parameter" alert
-hello(#server_hello{server_version = Version,
+hello(#server_hello{server_version = LegacyVersion,
+ random = Random,
+ cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId,
extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}}
+ #server_hello_selected_version{selected_version = Version} = HelloExt}
},
- #ssl_options{versions = SupportedVersions},
- _ConnectionStates0, _Renegotiation) ->
- case tls_record:is_higher({3,4}, Version) of
+ #ssl_options{versions = SupportedVersions} = SslOpt,
+ ConnectionStates0, Renegotiation) ->
+ %% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension
+ %% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version
+ %% number for TLS 1.2.
+ %% The "supported_versions" extension is supported from TLS 1.2.
+ case LegacyVersion > {3,3} orelse
+ LegacyVersion =:= {3,3} andalso Version < {3,3} of
true ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
false ->
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
- %% Implement TLS 1.3 statem ???
- ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION);
+ case Version of
+ {3,3} ->
+ %% TLS 1.2 ServerHello with "supported_versions" (special case)
+ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
+ Compression, HelloExt, SslOpt,
+ ConnectionStates0, Renegotiation);
+ {3,4} ->
+ %% TLS 1.3
+ {next_state, wait_sh}
+ end;
false ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
end
end;
-hello(#server_hello{server_version = Version, random = Random,
+hello(#server_hello{server_version = Version,
+ random = Random,
cipher_suite = CipherSuite,
compression_method = Compression,
- session_id = SessionId, extensions = HelloExt},
+ session_id = SessionId,
+ extensions = HelloExt},
#ssl_options{versions = SupportedVersions} = SslOpt,
ConnectionStates0, Renegotiation) ->
case tls_record:is_acceptable_version(Version, SupportedVersions) of
@@ -360,7 +379,7 @@ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) -
%%--------------------------------------------------------------------
enc_handshake(#hello_request{}, {3, N}) when N < 4 ->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor},
+enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
random = Random,
session_id = SessionID,
cipher_suites = CipherSuites,
@@ -371,7 +390,7 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 53f9adbbd3..6aeb9f5663 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -39,14 +39,23 @@
%% Create handshake messages
-export([certificate/5,
certificate_verify/4,
- encrypted_extensions/0,
- server_hello/4]).
+ encrypted_extensions/1]).
-export([do_start/2,
do_negotiated/2,
do_wait_cert/2,
do_wait_cv/2,
- do_wait_finished/2]).
+ do_wait_finished/2,
+ do_wait_sh/2,
+ do_wait_ee/2,
+ do_wait_cert_cr/2]).
+
+
+%% crypto:hash(sha256, "HelloRetryRequest").
+-define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17,
+ 190,29,140,2,30,101,184,145,
+ 194,162,17,22,122,187,140,94,
+ 7,158,9,226,200,168,51,156>>).
%%====================================================================
%% Create handshake messages
@@ -64,11 +73,24 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
extensions = Extensions
}.
+
+%% The server's extensions MUST contain "supported_versions".
+%% Additionally, it SHOULD contain the minimal set of extensions
+%% necessary for the client to generate a correct ClientHello pair. As
+%% with the ServerHello, a HelloRetryRequest MUST NOT contain any
+%% extensions that were not first offered by the client in its
+%% ClientHello, with the exception of optionally the "cookie" (see
+%% Section 4.2.2) extension.
+server_hello_extensions(hello_retry_request = MsgType, KeyShare) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
server_hello_extensions(MsgType, KeyShare) ->
SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
Extensions = #{server_hello_selected_version => SupportedVersions},
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare).
+
server_hello_random(server_hello, #security_parameters{server_random = Random}) ->
Random;
%% For reasons of backward compatibility with middleboxes (see
@@ -79,13 +101,17 @@ server_hello_random(server_hello, #security_parameters{server_random = Random})
%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
server_hello_random(hello_retry_request, _) ->
- crypto:hash(sha256, "HelloRetryRequest").
+ ?HELLO_RETRY_REQUEST_RANDOM.
-%% TODO: implement support for encrypted_extensions
-encrypted_extensions() ->
+encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) ->
#encrypted_extensions{
extensions = #{}
+ };
+encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) ->
+ Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol),
+ #encrypted_extensions{
+ extensions = Extensions
}.
@@ -119,7 +145,6 @@ filter_tls13_algs(Algo) ->
lists:filter(fun is_atom/1, Algo).
-%% TODO: use maybe monad for error handling!
%% enum {
%% X509(0),
%% RawPublicKey(2),
@@ -142,18 +167,28 @@ filter_tls13_algs(Algo) ->
%% opaque certificate_request_context<0..2^8-1>;
%% CertificateEntry certificate_list<0..2^24-1>;
%% } Certificate;
-certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) ->
+certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
CertList = chain_to_cert_list(Chain),
%% If this message is in response to a CertificateRequest, the value of
%% certificate_request_context in that message. Otherwise (in the case
%%of server authentication), this field SHALL be zero length.
- #certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = CertList};
- {error, Error} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList}};
+ {error, Error} when Role =:= server ->
+ {error, {no_suitable_certificates, Error}};
+ {error, _Error} when Role =:= client ->
+ %% The client MUST send a Certificate message if and only if the server
+ %% has requested client authentication via a CertificateRequest message
+ %% (Section 4.3.2). If the server requests client authentication but no
+ %% suitable certificate is available, the client MUST send a Certificate
+ %% message containing no certificates (i.e., with the "certificate_list"
+ %% field having length 0).
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []}}
end.
@@ -161,7 +196,7 @@ certificate_verify(PrivateKey, SignatureScheme,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
- tls_handshake_history = {Messages, _}}}, server) ->
+ tls_handshake_history = {Messages, _}}}, Role) ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
@@ -173,11 +208,11 @@ certificate_verify(PrivateKey, SignatureScheme,
%% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+ ContextString = context_string(Role),
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case sign(THash, <<"TLS 1.3, server CertificateVerify">>,
- HashAlgo, PrivateKey) of
+ case sign(THash, ContextString, HashAlgo, PrivateKey) of
{ok, Signature} ->
{ok, #certificate_verify_1_3{
algorithm = SignatureScheme,
@@ -252,6 +287,21 @@ encode_handshake(HandshakeMsg) ->
%% Decode handshake
%%====================================================================
+
+decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method),
+ ?UINT16(ExtLen), Extensions:ExtLen/binary>>)
+ when Random =:= ?HELLO_RETRY_REQUEST_RANDOM ->
+ HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor},
+ hello_retry_request),
+ #server_hello{
+ server_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method,
+ extensions = HelloExtensions};
decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) ->
Exts = decode_extensions(EncExts, certificate_request),
#certificate_request_1_3{
@@ -384,6 +434,15 @@ certificate_entry(DER) ->
%% 79
%% 00
%% 0101010101010101010101010101010101010101010101010101010101010101
+sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) ->
+ Content = build_content(Context, THash),
+ try public_key:sign(Content, HashAlgo, PrivateKey) of
+ Signature ->
+ {ok, Signature}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end;
sign(THash, Context, HashAlgo, PrivateKey) ->
Content = build_content(Context, THash),
@@ -401,7 +460,16 @@ sign(THash, Context, HashAlgo, PrivateKey) ->
end.
-verify(THash, Context, HashAlgo, Signature, PublicKey) ->
+verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+ Content = build_content(Context, THash),
+ try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of
+ Result ->
+ {ok, Result}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end;
+verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) ->
Content = build_content(Context, THash),
%% The length of the Salt MUST be equal to the length of the output
@@ -428,15 +496,16 @@ build_content(Context, THash) ->
%%====================================================================
+%% TLS Server
do_start(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
extensions = Extensions} = _Hello,
#state{connection_states = _ConnectionStates0,
ssl_options = #ssl_options{ciphers = ServerCiphers,
signature_algs = ServerSignAlgs,
- supported_groups = ServerGroups0},
+ supported_groups = ServerGroups0,
+ alpn_preferred_protocols = ALPNPreferredProtocols},
session = #session{own_certificate = Cert}} = State0) ->
-
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
ClientGroups = get_supported_groups(ClientGroups0),
ServerGroups = get_supported_groups(ServerGroups0),
@@ -444,23 +513,27 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
ClientShares0 = maps:get(key_share, Extensions, undefined),
ClientShares = get_key_shares(ClientShares0),
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
ClientSignAlgs = get_signature_scheme_list(
maps:get(signature_algs, Extensions, undefined)),
ClientSignAlgsCert = get_signature_scheme_list(
maps:get(signature_algs_cert, Extensions, undefined)),
- %% TODO: use library function if it exists
- %% Init the maybe "monad"
{Ref,Maybe} = maybe(),
try
+ %% Handle ALPN extension if ALPN is configured
+ ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
+
%% If the server does not select a PSK, then the server independently selects a
%% cipher suite, an (EC)DHE group and key share for key establishment,
%% and a signature algorithm/certificate pair to authenticate itself to
%% the client.
Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)),
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
- Maybe(validate_key_share(ClientGroups, ClientShares)),
+ Maybe(validate_client_key_share(ClientGroups, ClientShares)),
{PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
@@ -479,8 +552,14 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = update_start_state(State0, Cipher, KeyShare, SessionId,
- Group, SelectedSignAlg, ClientPubKey),
+ State1 = update_start_state(State0,
+ #{cipher => Cipher,
+ key_share => KeyShare,
+ session_id => SessionId,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ peer_public_key => ClientPubKey,
+ alpn => ALPNProtocol}),
%% 4.1.4. Hello Retry Request
%%
@@ -490,10 +569,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% the handshake.
Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId))
- %% TODO:
- %% - session handling
- %% - handle extensions: ALPN
- %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats)
+ %% TODO: session handling
catch
{Ref, {insufficient_security, no_suitable_groups}} ->
@@ -505,7 +581,87 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
{Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
{Ref, {insufficient_security, no_suitable_public_key}} ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key);
+ {Ref, no_application_protocol} ->
+ ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)
+ end;
+%% TLS Client
+do_start(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = _ServerHello,
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ transport_cb = Transport,
+ socket = Socket,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ tls_handshake_history = _HHistory} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #ssl_options{ciphers = ClientCiphers,
+ supported_groups = ClientGroups0} = SslOpts,
+ session = #session{own_certificate = Cert} = Session0,
+ connection_states = ConnectionStates0
+ } = State0) ->
+ ClientGroups = get_supported_groups(ClientGroups0),
+
+ {Ref,Maybe} = maybe(),
+ try
+ ServerKeyShare = maps:get(key_share, Extensions, undefined),
+ SelectedGroup = get_selected_group(ServerKeyShare),
+
+ %% Upon receipt of this extension in a HelloRetryRequest, the client
+ %% MUST verify that (1) the selected_group field corresponds to a group
+ %% which was provided in the "supported_groups" extension in the
+ %% original ClientHello and (2) the selected_group field does not
+ %% correspond to a group which was provided in the "key_share" extension
+ %% in the original ClientHello. If either of these checks fails, then
+ %% the client MUST abort the handshake with an "illegal_parameter"
+ %% alert.
+ Maybe(validate_selected_group(SelectedGroup, ClientGroups)),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+
+ %% Otherwise, when sending the new ClientHello, the client MUST
+ %% replace the original "key_share" extension with one containing only a
+ %% new KeyShareEntry for the group indicated in the selected_group field
+ %% of the triggering HelloRetryRequest.
+ ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert, ClientKeyShare),
+
+ HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions),
+
+ %% Update state
+ State1 = update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup}),
+
+ %% Replace ClientHello1 with a special synthetic handshake message
+ State2 = replace_ch1_with_message_hash(State1),
+ #state{handshake_env = #handshake_env{tls_handshake_history = HHistory}} = State2,
+
+ {BinMsg, ConnectionStates, Handshake} =
+ tls_connection:encode_handshake(Hello, HelloVersion, ConnectionStates0, HHistory),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
+
+ State = State2#state{
+ connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
+ session = Session0#session{session_id = Hello#client_hello.session_id},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
+ key_share = ClientKeyShare},
+
+ {State, wait_sh}
+
+ catch
+ {Ref, {illegal_parameter, Reason}} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason)
end.
@@ -515,7 +671,7 @@ do_negotiated(start_handshake,
own_certificate = OwnCert,
ecc = SelectedGroup,
sign_alg = SignatureScheme,
- dh_public_value = ClientKey},
+ dh_public_value = ClientPublicKey},
ssl_options = #ssl_options{} = SslOpts,
key_share = KeyShare,
handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
@@ -526,6 +682,8 @@ do_negotiated(start_handshake,
socket = _Socket,
transport_cb = _Transport}
} = State0) ->
+ ServerPrivateKey = get_server_private_key(KeyShare),
+
{Ref,Maybe} = maybe(),
try
@@ -536,12 +694,12 @@ do_negotiated(start_handshake,
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
State2 =
- calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1),
+ calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1),
State3 = ssl_record:step_encryption_state(State2),
%% Create EncryptedExtensions
- EncryptedExtensions = encrypted_extensions(),
+ EncryptedExtensions = encrypted_extensions(State2),
%% Encode EncryptedExtensions
State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
@@ -550,7 +708,7 @@ do_negotiated(start_handshake,
{State5, NextState} = maybe_send_certificate_request(State4, SslOpts),
%% Create Certificate
- Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
+ Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)),
%% Encode Certificate
State6 = tls_connection:queue_handshake(Certificate, State5),
@@ -574,14 +732,16 @@ do_negotiated(start_handshake,
catch
{Ref, badarg} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg})
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg});
+ {Ref, {no_suitable_certificates, Reason}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason})
end.
do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
{Ref,Maybe} = maybe(),
try
- Maybe(process_client_certificate(Certificate, State0))
+ Maybe(process_certificate(Certificate, State0))
catch
{Ref, {certificate_required, State}} ->
{?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State};
@@ -591,6 +751,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
{?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), State};
{Ref, {{handshake_failure, Reason}, State}} ->
{?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason), State};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State};
{#alert{} = Alert, State} ->
{Alert, State}
end.
@@ -599,8 +761,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
{Ref,Maybe} = maybe(),
try
- Maybe(verify_signature_algorithm(State0, CertificateVerify)),
- Maybe(verify_certificate_verify(State0, CertificateVerify))
+ State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)),
+ Maybe(verify_certificate_verify(State1, CertificateVerify))
catch
{Ref, {{bad_certificate, Reason}, State}} ->
{?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State};
@@ -610,20 +772,9 @@ do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
{?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State}
end.
-
+%% TLS Server
do_wait_finished(#finished{verify_data = VerifyData},
- #state{connection_states = _ConnectionStates0,
- session = #session{session_id = _SessionId,
- own_certificate = _OwnCert},
- ssl_options = #ssl_options{} = _SslOpts,
- key_share = _KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- static_env = #static_env{
- cert_db = _CertDbHandle,
- cert_db_ref = _CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
+ #state{static_env = #static_env{role = server}} = State0) ->
{Ref,Maybe} = maybe(),
@@ -639,19 +790,237 @@ do_wait_finished(#finished{verify_data = VerifyData},
catch
{Ref, decrypt_error} ->
?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)
+ end;
+%% TLS Client
+do_wait_finished(#finished{verify_data = _VerifyData},
+ #state{static_env = #static_env{role = client}} = State0) ->
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Maybe(validate_client_finished(State0, VerifyData)),
+
+ %% Maybe send Certificate + CertificateVerify
+ State1 = Maybe(maybe_queue_cert_cert_cv(State0)),
+
+ Finished = finished(State1),
+
+ %% Encode Finished
+ State2 = tls_connection:queue_handshake(Finished, State1),
+
+ %% Send first flight
+ {State3, _} = tls_connection:send_handshake_flight(State2),
+
+ State4 = calculate_traffic_secrets(State3),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State4)
+
+ catch
+ {Ref, decrypt_error} ->
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error);
+ {Ref, badarg} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg});
+ {Ref, {no_suitable_certificates, Reason}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason})
+ end.
+
+
+do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = ServerHello,
+ #state{key_share = ClientKeyShare0,
+ ssl_options = #ssl_options{ciphers = ClientCiphers,
+ supported_groups = ClientGroups0}} = State0) ->
+ ClientGroups = get_supported_groups(ClientGroups0),
+ ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ClientKeyShare = get_key_shares(ClientKeyShare0),
+
+ {Ref,Maybe} = maybe(),
+ try
+ %% Go to state 'start' if server replies with 'HelloRetryRequest'.
+ Maybe(maybe_hello_retry_request(ServerHello, State0)),
+
+ ServerKeyShare = get_key_shares(ServerKeyShare0),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+ Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
+
+ %% Get server public key
+ {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare),
+
+ {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare),
+
+ %% Update state
+ State1 = update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare0,
+ session_id => SessionId,
+ group => SelectedGroup,
+ peer_public_key => ServerPublicKey}),
+
+ State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1),
+
+ State3 = ssl_record:step_encryption_state(State2),
+
+ {State3, wait_ee}
+
+ catch
+ {Ref, {State, StateName, ServerHello}} ->
+ {State, StateName, ServerHello};
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ end.
+
+
+do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
+
+ ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
+ ALPNProtocol = get_alpn(ALPNProtocol0),
+
+ {Ref,_Maybe} = maybe(),
+
+ try
+ %% Update state
+ #state{handshake_env = HsEnv} = State0,
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
+
+ {State1, wait_cert_cr}
+ catch
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
end.
+do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(process_certificate(Certificate, State0))
+ catch
+ {Ref, {certificate_required, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required);
+ {Ref, {{certificate_unknown, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason);
+ {Ref, {{internal_error, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason);
+ {Ref, {{handshake_failure, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end;
+do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(process_certificate_request(CertificateRequest, State0))
+ catch
+ {Ref, {certificate_required, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required);
+ {Ref, {{certificate_unknown, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason);
+ {Ref, {illegal_parameter, Reason}} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason);
+ {Ref, {{internal_error, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason);
+ {Ref, {{handshake_failure, Reason}, _State}} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason)
+ end.
+
+
+
%% TODO: Remove this function!
%% not_implemented(State, Reason) ->
%% {error, {not_implemented, State, Reason}}.
-%%
+
%% not_implemented(update_secrets, State0, Reason) ->
%% State1 = calculate_traffic_secrets(State0),
%% State = ssl_record:step_encryption_state(State1),
%% {error, {not_implemented, State, Reason}}.
+%% For reasons of backward compatibility with middleboxes (see
+%% Appendix D.4), the HelloRetryRequest message uses the same structure
+%% as the ServerHello, but with Random set to the special value of the
+%% SHA-256 of "HelloRetryRequest":
+%%
+%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
+%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
+%%
+%% Upon receiving a message with type server_hello, implementations MUST
+%% first examine the Random value and, if it matches this value, process
+%% it as described in Section 4.1.4).
+maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) ->
+ {error, {State0, start, ServerHello}};
+maybe_hello_retry_request(_, _) ->
+ ok.
+
+
+maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) ->
+ {ok, State};
+maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ static_env = #static_env{
+ role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ %% Create Certificate
+ Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)),
+
+ %% Encode Certificate
+ State1 = tls_connection:queue_handshake(Certificate, State0),
+
+ %% Maybe create and queue CertificateVerify
+ State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
+ {ok, State}
+ catch
+ {Ref, badarg} ->
+ {error, badarg}
+ end.
+
+
+%% Clients MUST send this message whenever authenticating via a certificate
+%% (i.e., when the Certificate message is non-empty).
+maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
+ {ok, State};
+maybe_queue_cert_verify(_Certificate,
+ #state{connection_states = _ConnectionStates0,
+ session = #session{sign_alg = SignatureScheme},
+ connection_env = #connection_env{private_key = CertPrivateKey},
+ static_env = #static_env{role = client}
+ } = State) ->
+ {Ref,Maybe} = maybe(),
+ try
+ CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
+ {ok, tls_connection:queue_handshake(CertificateVerify, State)}
+ catch
+ {Ref, badarg} ->
+ {error, badarg}
+ end.
+
%% Recipients of Finished messages MUST verify that the contents are
%% correct and if incorrect MUST terminate the connection with a
@@ -684,7 +1053,7 @@ send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0),
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
- %% TODO: Fix handshake history!
+ %% Update handshake history
State2 = replace_ch1_with_message_hash(State1),
{ok, {State2, start}};
@@ -703,19 +1072,44 @@ maybe_send_certificate_request(State, #ssl_options{
{tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
-process_client_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #ssl_options{
- fail_if_no_peer_cert = false}} = State) ->
+process_certificate_request(#certificate_request_1_3{},
+ #state{session = #session{own_certificate = undefined}} = State) ->
+ {ok, {State#state{client_certificate_requested = true}, wait_cert}};
+
+process_certificate_request(#certificate_request_1_3{
+ extensions = Extensions},
+ #state{session = #session{own_certificate = Cert} = Session} = State) ->
+ ServerSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ServerSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
+
+ %% Check if server supports signature algorithm of client certificate
+ case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of
+ ok ->
+ {ok, {State#state{client_certificate_requested = true}, wait_cert}};
+ {error, _} ->
+ %% Certificate not supported: send empty certificate in state 'wait_finished'
+ {ok, {State#state{client_certificate_requested = true,
+ session = Session#session{own_certificate = undefined}}, wait_cert}}
+ end.
+
+
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = false}} = State) ->
{ok, {State, wait_finished}};
-process_client_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #ssl_options{
- fail_if_no_peer_cert = true}} = State0) ->
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = true}} = State0) ->
%% At this point the client believes that the connection is up and starts using
%% its traffic secrets. In order to be able send an proper Alert to the client
@@ -724,19 +1118,18 @@ process_client_certificate(#certificate_1_3{
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
{error, {certificate_required, State}};
-process_client_certificate(#certificate_1_3{certificate_list = Certs0},
- #state{ssl_options =
- #ssl_options{signature_algs = SignAlgs,
- signature_algs_cert = SignAlgsCert} = SslOptions,
- static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbHandle}} = State0) ->
+process_certificate(#certificate_1_3{certificate_list = Certs0},
+ #state{ssl_options =
+ #ssl_options{signature_algs = SignAlgs,
+ signature_algs_cert = SignAlgsCert} = SslOptions,
+ static_env =
+ #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbHandle}} = State0) ->
%% TODO: handle extensions!
-
%% Remove extensions from list of certificates!
Certs = convert_certificate_chain(Certs0),
case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of
@@ -747,13 +1140,11 @@ process_client_certificate(#certificate_1_3{certificate_list = Certs0},
State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
{ok, {State, wait_cv}};
{error, Reason} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
+ State = update_encryption_state(Role, State0),
{error, {Reason, State}};
- #alert{} = Alert ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {Alert, State}
+ {ok, #alert{} = Alert} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}}
end;
false ->
State1 = calculate_traffic_secrets(State0),
@@ -777,6 +1168,17 @@ is_supported_signature_algorithm([BinCert|_], SignAlgs0) ->
lists:member(Scheme, SignAlgs).
+%% Sets correct encryption state when sending Alerts in shared states that use different secrets.
+%% - If client: use handshake secrets.
+%% - If server: use traffic secrets as by this time the client's state machine
+%% already stepped into the 'connection' state.
+update_encryption_state(server, State0) ->
+ State1 = calculate_traffic_secrets(State0),
+ ssl_record:step_encryption_state(State1);
+update_encryption_state(client, State) ->
+ State.
+
+
validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, Host) ->
ServerName = ssl_handshake:server_name(SslOptions#ssl_options.server_name_indication, Host, Role),
[PeerCert | ChainCerts ] = Certs,
@@ -797,9 +1199,9 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHand
{ok, {PublicKeyInfo,_}} ->
{ok, {PeerCert, PublicKeyInfo}};
{error, Reason} ->
- ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
- SslOptions, Options,
- CertDbHandle, CertDbRef)
+ {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
+ SslOptions, Options,
+ CertDbHandle, CertDbRef)}
end
catch
error:{badmatch,{error, {asn1, Asn1Reason}}} ->
@@ -861,7 +1263,7 @@ message_hash(ClientHello1, HKDFAlgo) ->
crypto:hash(HKDFAlgo, ClientHello1)].
-calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
+calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
@@ -874,13 +1276,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
%% Calculate handshake_secret
PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)),
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
- PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
- IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
+ IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup),
HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
%% Calculate [sender]_handshake_traffic_secret
{Messages, _} = HHistory,
+
ClientHSTrafficSecret =
tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
ServerHSTrafficSecret =
@@ -899,10 +1301,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey).
-calculate_traffic_secrets(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = HHistory}} = State0) ->
+
+calculate_traffic_secrets(#state{
+ static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDFAlgo,
@@ -913,7 +1318,7 @@ calculate_traffic_secrets(#state{connection_states = ConnectionStates,
tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
%% Get the correct list messages for the handshake context.
- Messages = get_handshake_context(HHistory),
+ Messages = get_handshake_context(Role, HHistory),
%% Calculate [sender]_application_traffic_secret_0
ClientAppTrafficSecret0 =
@@ -942,11 +1347,6 @@ get_private_key(#key_share_entry{
{_, PrivateKey}}) ->
PrivateKey.
-%% TODO: implement EC keys
-get_public_key({?'rsaEncryption', PublicKey, _}) ->
- PublicKey.
-
-
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
when is_binary(OthersKey) andalso is_binary(MyKey) andalso
@@ -966,9 +1366,11 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
public_key:compute_key(Point, MyKey).
-update_pending_connection_states(#state{connection_states =
- CS = #{pending_read := PendingRead0,
- pending_write := PendingWrite0}} = State,
+update_pending_connection_states(#state{
+ static_env = #static_env{role = server},
+ connection_states =
+ CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0}} = State,
HandshakeSecret,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey) ->
@@ -977,8 +1379,23 @@ update_pending_connection_states(#state{connection_states =
PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
WriteKey, WriteIV, WriteFinishedKey),
State#state{connection_states = CS#{pending_read => PendingRead,
+ pending_write => PendingWrite}};
+update_pending_connection_states(#state{
+ static_env = #static_env{role = client},
+ connection_states =
+ CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0}} = State,
+ HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ WriteKey, WriteIV, WriteFinishedKey),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey),
+ State#state{connection_states = CS#{pending_read => PendingRead,
pending_write => PendingWrite}}.
+
update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
HandshakeSecret, Key, IV, FinishedKey) ->
%% Store secret
@@ -988,11 +1405,24 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam
cipher_state => cipher_init(Key, IV, FinishedKey)}.
+update_start_state(State, Map) ->
+ Cipher = maps:get(cipher, Map, undefined),
+ KeyShare = maps:get(key_share, Map, undefined),
+ SessionId = maps:get(session_id, Map, undefined),
+ Group = maps:get(group, Map, undefined),
+ SelectedSignAlg = maps:get(sign_alg, Map, undefined),
+ PeerPublicKey = maps:get(peer_public_key, Map, undefined),
+ ALPNProtocol = maps:get(alpn, Map, undefined),
+ update_start_state(State, Cipher, KeyShare, SessionId,
+ Group, SelectedSignAlg, PeerPublicKey,
+ ALPNProtocol).
+%%
update_start_state(#state{connection_states = ConnectionStates0,
+ handshake_env = #handshake_env{} = HsEnv,
connection_env = CEnv,
session = Session} = State,
Cipher, KeyShare, SessionId,
- Group, SelectedSignAlg, ClientPubKey) ->
+ Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) ->
#{security_parameters := SecParamsR0} = PendingRead =
maps:get(pending_read, ConnectionStates0),
#{security_parameters := SecParamsW0} = PendingWrite =
@@ -1003,11 +1433,12 @@ update_start_state(#state{connection_states = ConnectionStates0,
ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
pending_write => PendingWrite#{security_parameters => SecParamsW}},
State#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol},
key_share = KeyShare,
session = Session#session{session_id = SessionId,
ecc = Group,
sign_alg = SelectedSignAlg,
- dh_public_value = ClientPubKey,
+ dh_public_value = PeerPublicKey,
cipher_suite = Cipher},
connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
@@ -1071,25 +1502,41 @@ get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) ->
%%
%% Drop all client messages from the front of the iolist using the property that
%% incoming messages are binaries.
-get_handshake_context({Messages, _}) ->
- get_handshake_context(Messages);
-get_handshake_context([H|T]) when is_binary(H) ->
- get_handshake_context(T);
-get_handshake_context(L) ->
+get_handshake_context(server, {Messages, _}) ->
+ get_handshake_context_server(Messages);
+get_handshake_context(client, {Messages, _}) ->
+ get_handshake_context_client(Messages).
+
+get_handshake_context_server([H|T]) when is_binary(H) ->
+ get_handshake_context_server(T);
+get_handshake_context_server(L) ->
L.
+get_handshake_context_client([H|T]) when is_list(H) ->
+ get_handshake_context_client(T);
+get_handshake_context_client(L) ->
+ L.
+
+
+%% If the CertificateVerify message is sent by a server, the signature
+%% algorithm MUST be one offered in the client's "signature_algorithms"
+%% extension unless no valid certificate chain can be produced without
+%% unsupported algorithms
+%%
%% If sent by a client, the signature algorithm used in the signature
%% MUST be one of those present in the supported_signature_algorithms
%% field of the "signature_algorithms" extension in the
%% CertificateRequest message.
-verify_signature_algorithm(#state{ssl_options =
- #ssl_options{
- signature_algs = ServerSignAlgs}} = State0,
- #certificate_verify_1_3{algorithm = ClientSignAlg}) ->
- case lists:member(ClientSignAlg, ServerSignAlgs) of
+verify_signature_algorithm(#state{
+ static_env = #static_env{role = Role},
+ ssl_options =
+ #ssl_options{
+ signature_algs = LocalSignAlgs}} = State0,
+ #certificate_verify_1_3{algorithm = PeerSignAlg}) ->
+ case lists:member(PeerSignAlg, LocalSignAlgs) of
true ->
- ok;
+ {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)};
false ->
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
@@ -1098,11 +1545,19 @@ verify_signature_algorithm(#state{ssl_options =
end.
-verify_certificate_verify(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- public_key_info = PublicKeyInfo,
- tls_handshake_history = HHistory}} = State0,
+maybe_update_selected_sign_alg(#state{session = Session} = State, SignAlg, client) ->
+ State#state{session = Session#session{sign_alg = SignAlg}};
+maybe_update_selected_sign_alg(State, _, _) ->
+ State.
+
+
+verify_certificate_verify(#state{
+ static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ public_key_info = PublicKeyInfo,
+ tls_handshake_history = HHistory}} = State0,
#certificate_verify_1_3{
algorithm = SignatureScheme,
signature = Signature}) ->
@@ -1120,12 +1575,11 @@ verify_certificate_verify(#state{connection_states = ConnectionStates,
%% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
THash = tls_v1:transcript_hash(Context, HKDFAlgo),
- PublicKey = get_public_key(PublicKeyInfo),
+ ContextString = peer_context_string(Role),
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case verify(THash, <<"TLS 1.3, client CertificateVerify">>,
- HashAlgo, Signature, PublicKey) of
+ case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of
{ok, true} ->
{ok, {State0, wait_finished}};
{ok, false} ->
@@ -1139,6 +1593,19 @@ verify_certificate_verify(#state{connection_states = ConnectionStates,
end.
+context_string(server) ->
+ <<"TLS 1.3, server CertificateVerify">>;
+context_string(client) ->
+ <<"TLS 1.3, client CertificateVerify">>.
+
+
+%% Return context string for verifing peer signature
+peer_context_string(server) ->
+ <<"TLS 1.3, client CertificateVerify">>;
+peer_context_string(client) ->
+ <<"TLS 1.3, server CertificateVerify">>.
+
+
%% If there is no overlap between the received
%% "supported_groups" and the groups supported by the server, then the
%% server MUST abort the handshake with a "handshake_failure" or an
@@ -1172,14 +1639,36 @@ select_common_groups(ServerGroups, ClientGroups) ->
%% for groups not listed in the client's "supported_groups" extension.
%% Servers MAY check for violations of these rules and abort the
%% handshake with an "illegal_parameter" alert if one is violated.
-validate_key_share(_ ,[]) ->
+validate_client_key_share(_ ,[]) ->
ok;
-validate_key_share([], _) ->
+validate_client_key_share([], _) ->
{error, illegal_parameter};
-validate_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
- validate_key_share(ClientGroups, ClientShares);
-validate_key_share([_|ClientGroups], [_|_] = ClientShares) ->
- validate_key_share(ClientGroups, ClientShares).
+validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
+ validate_client_key_share(ClientGroups, ClientShares);
+validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_client_key_share(ClientGroups, ClientShares).
+
+
+%% Verify that selected group is offered by the client.
+validate_server_key_share([G|_ClientGroups], {_, G, _}) ->
+ ok;
+validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) ->
+ validate_server_key_share(ClientGroups, ServerKeyShare).
+
+
+validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
+ {error, {illegal_parameter,
+ "Selected group sent by the server shall not correspond to a group"
+ " which was provided in the key_share extension"}};
+validate_selected_group(SelectedGroup, ClientGroups) ->
+ case lists:member(SelectedGroup, ClientGroups) of
+ true ->
+ ok;
+ false ->
+ {error, {illegal_parameter,
+ "Selected group sent by the server shall correspond to a group"
+ " which was provided in the supported_groups extension"}}
+ end.
get_client_public_key([Group|_] = Groups, ClientShares) ->
@@ -1197,20 +1686,50 @@ get_client_public_key([Group|Groups], ClientShares, PreferredGroup) ->
get_client_public_key(Groups, ClientShares, PreferredGroup)
end.
+get_client_private_key([Group|_] = Groups, ClientShares) ->
+ get_client_private_key(Groups, ClientShares, Group).
+%%
+get_client_private_key(_, [], PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_private_key([], _, PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+get_client_private_key([Group|Groups], ClientShares, PreferredGroup) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, {_, _, {_, ClientPrivateKey}}} ->
+ {Group, ClientPrivateKey};
+ {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} ->
+ {Group, ClientPrivateKey};
+ false ->
+ get_client_private_key(Groups, ClientShares, PreferredGroup)
+ end.
+
+
+get_server_public_key({key_share_entry, Group, PublicKey}) ->
+ {Group, PublicKey}.
+
+
+%% RFC 7301 - Application-Layer Protocol Negotiation Extension
+%% It is expected that a server will have a list of protocols that it
+%% supports, in preference order, and will only select a protocol if the
+%% client supports it. In that case, the server SHOULD select the most
+%% highly preferred protocol that it supports and that is also
+%% advertised by the client. In the event that the server supports no
+%% protocols that the client advertises, then the server SHALL respond
+%% with a fatal "no_application_protocol" alert.
+handle_alpn(undefined, _) ->
+ {ok, undefined};
+handle_alpn([], _) ->
+ {error, no_application_protocol};
+handle_alpn([_|_], undefined) ->
+ {ok, undefined};
+handle_alpn([ServerProtocol|T], ClientProtocols) ->
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true ->
+ {ok, ServerProtocol};
+ false ->
+ handle_alpn(T, ClientProtocols)
+ end.
-%% get_client_public_key(Group, ClientShares) ->
-%% case lists:keysearch(Group, 2, ClientShares) of
-%% {value, {_, _, ClientPublicKey}} ->
-%% ClientPublicKey;
-%% false ->
-%% %% 4.1.4. Hello Retry Request
-%% %%
-%% %% The server will send this message in response to a ClientHello
-%% %% message if it is able to find an acceptable set of parameters but the
-%% %% ClientHello does not contain sufficient information to proceed with
-%% %% the handshake.
-%% no_suitable_key
-%% end.
select_cipher_suite([], _) ->
{error, no_suitable_cipher};
@@ -1223,6 +1742,19 @@ select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
select_cipher_suite(ClientCiphers, ServerCiphers)
end.
+
+%% RFC 8446 4.1.3 ServerHello
+%% A client which receives a cipher suite that was not offered MUST abort the
+%% handshake with an "illegal_parameter" alert.
+validate_cipher_suite(Cipher, ClientCiphers) ->
+ case lists:member(Cipher, ClientCiphers) of
+ true ->
+ ok;
+ false ->
+ {error, illegal_parameter}
+ end.
+
+
%% RFC 8446 (TLS 1.3)
%% TLS 1.3 provides two extensions for indicating which signature
%% algorithms may be used in digital signatures. The
@@ -1246,15 +1778,20 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
%% DSA keys are not supported by TLS 1.3
select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
{error, {insufficient_security, no_suitable_public_key}};
-%% TODO: Implement support for ECDSA keys!
select_sign_algo(_, [], _) ->
{error, {insufficient_security, no_suitable_signature_algorithm}};
select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
{_, S, _} = ssl_cipher:scheme_to_components(C),
%% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
%% TLS handshake messages: filter sha-1 and rsa_pkcs1.
+ %%
+ %% RSASSA-PSS RSAE algorithms: If the public key is carried in an X.509
+ %% certificate, it MUST use the rsaEncryption OID.
+ %% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate,
+ %% it MUST use the RSASSA-PSS OID.
case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
- orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae))
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss)
+ orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa))
andalso
lists:member(C, ServerSignAlgs) of
true ->
@@ -1331,7 +1868,20 @@ get_supported_groups(#supported_groups{supported_groups = Groups}) ->
Groups.
get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
- ClientShares.
+ ClientShares;
+get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
+ ServerShare.
+
+get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) ->
+ SelectedGroup.
+
+get_alpn(ALPNProtocol0) ->
+ case ssl_handshake:decode_alpn(ALPNProtocol0) of
+ undefined ->
+ undefined;
+ [ALPNProtocol] ->
+ ALPNProtocol
+ end.
maybe() ->
Ref = erlang:make_ref(),
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index a5c550a429..2aeab98929 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -514,16 +514,27 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0, SslOpts, Acc, Type, Versi
end.
-binary_from_front(SplitSize, {Front,Size,Rear}) ->
+binary_from_front(0, Q) ->
+ {<<>>, Q};
+binary_from_front(SplitSize, {Front,Size,Rear}) when SplitSize =< Size ->
binary_from_front(SplitSize, Front, Size, Rear, []).
%%
-binary_from_front(SplitSize, [], Size, [_] = Rear, Acc) ->
- %% Optimize a simple case
- binary_from_front(SplitSize, Rear, Size, [], Acc);
+%% SplitSize > 0 and there is at least SplitSize bytes buffered in Front and Rear
binary_from_front(SplitSize, [], Size, Rear, Acc) ->
- binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc);
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ binary_from_front(SplitSize, Rear, Size, [], Acc);
+ [Bin2,Bin1] ->
+ binary_from_front(SplitSize, [Bin1,Bin2], Size, [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ binary_from_front(SplitSize, [Bin1,Bin2,Bin3], Size, [], Acc);
+ [_,_,_|_] ->
+ binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc)
+ end;
binary_from_front(SplitSize, [Bin|Front], Size, Rear, []) ->
- %% Optimize a frequent case
+ %% Optimize the frequent case when the accumulator is empty
BinSize = byte_size(Bin),
if
SplitSize < BinSize ->