diff options
Diffstat (limited to 'lib/ssl/src')
| -rw-r--r-- | lib/ssl/src/dtls_handshake.erl | 3 | ||||
| -rw-r--r-- | lib/ssl/src/ssl.erl | 10 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_cipher.erl | 30 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_connection.erl | 11 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 154 | ||||
| -rw-r--r-- | lib/ssl/src/tls_handshake.erl | 4 | ||||
| -rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 71 | ||||
| -rw-r--r-- | lib/ssl/src/tls_handshake_1_3.hrl | 47 | ||||
| -rw-r--r-- | lib/ssl/src/tls_record_1_3.erl | 9 |
9 files changed, 266 insertions, 73 deletions
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/ssl.erl b/lib/ssl/src/ssl.erl index 20b1e85ceb..7ff9aed8ea 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -128,7 +128,8 @@ tls_alert/0, srp_param_type/0, named_curve/0, - sign_scheme/0]). + sign_scheme/0, + group/0]). %% ------------------------------------------------------------------------------------------------------- @@ -243,7 +244,7 @@ secp160r2. % exported -type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 | - ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. + ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported -type srp_param_type() :: srp_1024 | srp_1536 | @@ -296,6 +297,7 @@ {ciphers, cipher_suites()} | {eccs, [named_curve()]} | {signature_algs_cert, signature_schemes()} | + {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | @@ -342,6 +344,7 @@ -type protocol_versions() :: [protocol_version()]. -type signature_algs() :: [{hash(), sign_algo()}]. -type signature_schemes() :: [sign_scheme()]. +-type supported_groups() :: [group()]. -type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: any()}. -type padding_check() :: boolean(). -type beast_mitigation() :: one_n_minus_one | zero_n | disabled. @@ -979,7 +982,8 @@ cipher_suites(all) -> %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version) when Version == 'tlsv1.2'; +cipher_suites(Base, Version) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index f4a91cac52..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]. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index de12de646c..2483509228 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1275,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_handshake.erl b/lib/ssl/src/ssl_handshake.erl index c6698bc74a..bd2efa9fbb 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, @@ -534,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>>}; @@ -589,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) -> @@ -707,7 +709,25 @@ encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> Group = tls_v1:group_to_enum(Group0), encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), - ?UINT16(2), ?UINT16(Group), Acc/binary>>). + ?UINT16(2), ?UINT16(Group), Acc/binary>>); +encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> + KEModes = encode_psk_key_exchange_modes(KEModes0), + KEModesLen = byte_size(KEModes), + ExtLen = KEModesLen + 1, + encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), + ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = Identities0, + binders = Binders0} = PSKs} | Rest], Acc) -> + Identities = encode_psk_identities(Identities0), + Binders = encode_psk_binders(Binders0), + Len = byte_size(Identities) + byte_size(Binders), + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(Len), Identities/binary, Binders/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(2), ?UINT16(Identity), Acc/binary>>). encode_client_protocol_negotiation(undefined, _) -> @@ -1256,6 +1276,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), @@ -1486,8 +1508,12 @@ extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) -> Schemes; extension_value(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; +extension_value(#key_share_server_hello{server_share = ServerShare}) -> + ServerShare; extension_value(#client_hello_versions{versions = Versions}) -> - Versions. + Versions; +extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) -> + SelectedVersion. %%-------------------------------------------------------------------- @@ -2089,6 +2115,41 @@ encode_key_share_entry(#key_share_entry{ Len = byte_size(KeyExchange), <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>. +encode_psk_key_exchange_modes(KEModes) -> + encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>). +%% +encode_psk_key_exchange_modes([], Acc) -> + Acc; +encode_psk_key_exchange_modes([psk_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>); +encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>). + + +encode_psk_identities(Identities) -> + encode_psk_identities(Identities, <<>>). +%% +encode_psk_identities([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_identities([#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|T], Acc) -> + IdLen = byte_size(Identity), + encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,Age/binary>>). + + +encode_psk_binders(Binders) -> + encode_psk_binders(Binders, <<>>). +%% +encode_psk_binders([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_binders([Binder|T], Acc) -> + Len = byte_size(Binder), + encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>). + + hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2441,6 +2502,33 @@ decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), #key_share_hello_retry_request{ selected_group = tls_v1:enum_to_group(Group)}}); +decode_extensions(<<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> + <<?BYTE(PLen),KEModes:PLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{psk_key_exchange_modes => + #psk_key_exchange_modes{ + ke_modes = decode_psk_key_exchange_modes(KEModes)}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = client_hello, Acc) -> + <<?UINT16(IdLen),Identities:IdLen/binary,?UINT16(BLen),Binders:BLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = decode_psk_identities(Identities), + binders = decode_psk_binders(Binders)}}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = server_hello, Acc) -> + <<?UINT16(Identity)>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_server_hello{ + selected_identity = Identity}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. @@ -2500,6 +2588,38 @@ decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> decode_protocols(_Bytes, _Acc) -> {error, invalid_protocols}. + +decode_psk_key_exchange_modes(KEModes) -> + decode_psk_key_exchange_modes(KEModes, []). +%% +decode_psk_key_exchange_modes(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_DHE_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]). + + +decode_psk_identities(Identities) -> + decode_psk_identities(Identities, []). +%% +decode_psk_identities(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, Age:4/binary, Rest/binary>>, Acc) -> + decode_psk_identities(Rest, [#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|Acc]). + + +decode_psk_binders(Binders) -> + decode_psk_binders(Binders, []). +%% +decode_psk_binders(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) -> + decode_psk_binders(Rest, [Binder|Acc]). + + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -2666,7 +2786,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, @@ -2936,11 +3056,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)}; @@ -2948,10 +3068,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), @@ -2962,7 +3082,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 @@ -2978,11 +3098,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})); @@ -3036,7 +3158,7 @@ empty_extensions({3,4}, client_hello) -> %% padding => undefined, key_share => undefined, pre_shared_key => undefined, - %% psk_key_exhange_modes => undefined, + psk_key_exchange_modes => undefined, %% early_data => undefined, %% cookie => undefined, client_hello_versions => undefined, @@ -3065,6 +3187,8 @@ empty_extensions({3,4}, hello_retry_request) -> 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/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index c132f75eae..37265e0759 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -379,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, @@ -390,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 49d20b3ec0..c29366e717 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -39,7 +39,7 @@ %% Create handshake messages -export([certificate/5, certificate_verify/4, - encrypted_extensions/0]). + encrypted_extensions/1]). -export([do_start/2, do_negotiated/2, @@ -61,10 +61,10 @@ %% Create handshake messages %%==================================================================== -server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> +server_hello(MsgType, SessionId, KeyShare, ConnectionStates) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - Extensions = server_hello_extensions(MsgType, KeyShare, ALPN), + Extensions = server_hello_extensions(MsgType, KeyShare), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = 0, %% legacy attribute @@ -73,6 +73,7 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> 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 @@ -80,18 +81,14 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> %% 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, _) -> +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, undefined) -> +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_extensions(MsgType, KeyShare, ALPN0) -> - Extensions0 = ssl_handshake:add_selected_version(#{}), %% {3,4} (TLS 1.3) - Extensions1 = ssl_handshake:add_alpn(Extensions0, ALPN0), - ssl_handshake:add_server_share(MsgType, Extensions1, KeyShare). + ssl_handshake:add_server_share(MsgType, Extensions, KeyShare). server_hello_random(server_hello, #security_parameters{server_random = Random}) -> @@ -107,10 +104,14 @@ server_hello_random(hello_retry_request, _) -> ?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 }. @@ -503,7 +504,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ssl_options = #ssl_options{ciphers = ServerCiphers, signature_algs = ServerSignAlgs, supported_groups = ServerGroups0, - alpn_preferred_protocols = ALPNPreferredProtocols}, + alpn_preferred_protocols = ALPNPreferredProtocols, + honor_cipher_order = HonorCipherOrder}, session = #session{own_certificate = Cert}} = State0) -> ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), @@ -530,7 +532,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% 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)), + Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), @@ -673,8 +675,7 @@ do_negotiated(start_handshake, dh_public_value = ClientPublicKey}, ssl_options = #ssl_options{} = SslOpts, key_share = KeyShare, - handshake_env = #handshake_env{tls_handshake_history = _HHistory0, - alpn = ALPN}, + handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, connection_env = #connection_env{private_key = CertPrivateKey}, static_env = #static_env{ cert_db = CertDbHandle, @@ -689,7 +690,7 @@ do_negotiated(start_handshake, try %% Create server_hello %% Extensions: supported_versions, key_share, (pre_shared_key) - ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0, ALPN), + ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), @@ -699,7 +700,7 @@ do_negotiated(start_handshake, 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), @@ -881,12 +882,19 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, end. -do_wait_ee(#encrypted_extensions{extensions = _Extensions}, State0) -> +do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> + + ALPNProtocol0 = maps:get(alpn, Extensions, undefined), + ALPNProtocol = get_alpn(ALPNProtocol0), {Ref,_Maybe} = maybe(), try - {State0, wait_cert_cr} + %% 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); @@ -1041,10 +1049,9 @@ compare_verify_data(_, _) -> {error, decrypt_error}. -send_hello_retry_request(#state{connection_states = ConnectionStates0, - handshake_env = #handshake_env{alpn = ALPN}} = State0, +send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, no_suitable_key, KeyShare, SessionId) -> - ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0, ALPN), + ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), %% Update handshake history @@ -1725,15 +1732,19 @@ handle_alpn([ServerProtocol|T], ClientProtocols) -> end. -select_cipher_suite([], _) -> +select_cipher_suite(_, [], _) -> {error, no_suitable_cipher}; -select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> +%% If honor_cipher_order is set to true, use the server's preference for +%% cipher suite selection. +select_cipher_suite(true, ClientCiphers, ServerCiphers) -> + select_cipher_suite(false, ServerCiphers, ClientCiphers); +select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> - select_cipher_suite(ClientCiphers, ServerCiphers) + select_cipher_suite(false, ClientCiphers, ServerCiphers) end. @@ -1869,6 +1880,14 @@ get_key_shares(#key_share_server_hello{server_share = 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(), Ok = fun(ok) -> ok; diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 7ae1b93e1c..eb85f216c8 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -74,29 +74,52 @@ y % opaque Y[coordinate_length]; }). +%% RFC 8446 4.2.9. Pre-Shared Key Exchange Modes + +%% enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; -define(PSK_KE, 0). -define(PSK_DHE_KE, 1). --record(psk_keyexchange_modes, { +-record(psk_key_exchange_modes, { ke_modes % ke_modes<1..255> }). + +%% RFC 8446 4.2.10. Early Data Indication -record(empty, { }). -record(early_data_indication, { indication % uint32 max_early_data_size (new_session_ticket) | %% #empty{} (client_hello, encrypted_extensions) }). --record(psk_identity, { - identity, % opaque identity<1..2^16-1> - obfuscated_ticket_age % uint32 - }). --record(offered_psks, { - psk_identity, %identities<7..2^16-1>; - psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255> - }). --record(pre_shared_keyextension,{ - extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello) - }). + +%% RFC 8446 4.2.11. Pre-Shared Key Extension +-record(psk_identity, + { + identity, % opaque identity<1..2^16-1> + obfuscated_ticket_age % uint32 + }). + +-record(offered_psks, + { + identities, % PskIdentity identities<7..2^16-1>; + binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255> + }). + +%% struct { +%% select (Handshake.msg_type) { +%% case client_hello: OfferedPsks; +%% case server_hello: uint16 selected_identity; +%% }; +%% } PreSharedKeyExtension; +-record(pre_shared_key_client_hello, + { + offered_psks + }). + +-record(pre_shared_key_server_hello, + { + selected_identity + }). %% RFC 8446 B.3.1.2. -record(cookie, { diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 74321a1ae2..d713062284 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -138,6 +138,15 @@ decode_cipher_text(#ssl_tls{type = ?ALERT, {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version fragment = <<2,47>>}, ConnectionStates0}; +%% TLS 1.3 server can receive a User Cancelled Alert when handshake is +%% paused and then cancelled on the client side. +decode_cipher_text(#ssl_tls{type = ?ALERT, + version = ?LEGACY_VERSION, + fragment = <<2,90>>}, + ConnectionStates0) -> + {#ssl_tls{type = ?ALERT, + version = {3,4}, %% Internally use real version + fragment = <<2,90>>}, ConnectionStates0}; %% RFC8446 - TLS 1.3 %% D.4. Middlebox Compatibility Mode %% - If not offering early data, the client sends a dummy |
