From 7240ecfc3ac931cfc7700f1b7e83d75353296c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 9 May 2019 16:32:35 +0200 Subject: ssl: Implement basic TLS 1.3 client --- lib/ssl/src/tls_connection.erl | 11 +- lib/ssl/src/tls_connection_1_3.erl | 53 ++++++- lib/ssl/src/tls_handshake.erl | 39 ++++-- lib/ssl/src/tls_handshake_1_3.erl | 275 ++++++++++++++++++++++++++++++++++--- 4 files changed, 343 insertions(+), 35 deletions(-) diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 61281a3fb2..9c8c3b9352 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -394,6 +394,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) -> @@ -659,10 +660,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); diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 76cdebc76f..a6b589236f 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 ]). @@ -183,3 +186,51 @@ 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{}, State0, _Module) -> + {Record, State} = tls_connection:next_record(State0), + tls_connection:next_event(?FUNCTION_NAME, 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} -> + %% TODO: Implement hello_retry_request + {next_state, start, State1, []}; + {State1, wait_ee} -> + {Record, State} = tls_connection:next_record(State1), + tls_connection:next_event(wait_ee, Record, State) + end; +wait_sh(Type, Msg, State, Connection) -> + ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). + + +wait_ee(internal, #change_cipher_spec{}, State0, _Module) -> + {Record, State} = tls_connection:next_record(State0), + tls_connection:next_event(?FUNCTION_NAME, 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} -> + {Record, State} = tls_connection:next_record(State1), + tls_connection:next_event(NextState, Record, State) + 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{}, State0, _Module) -> + {Record, State} = tls_connection:next_record(State0), + tls_connection:next_event(?FUNCTION_NAME, 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 -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cv, State0); + {State1, NextState} -> + {Record, State} = tls_connection:next_record(State1), + tls_connection:next_event(NextState, Record, State) + 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..c132f75eae 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 diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 8a4ad922e1..d7f45d984a 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -46,7 +46,10 @@ 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]). %%==================================================================== %% Create handshake messages @@ -79,6 +82,9 @@ 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, _) -> + hello_retry_request_random(). + +hello_retry_request_random() -> crypto:hash(sha256, "HelloRetryRequest"). @@ -436,7 +442,6 @@ do_start(#client_hello{cipher_suites = ClientCiphers, signature_algs = ServerSignAlgs, supported_groups = ServerGroups0}, session = #session{own_certificate = Cert}} = State0) -> - ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), ServerGroups = get_supported_groups(ServerGroups0), @@ -515,7 +520,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 +531,8 @@ do_negotiated(start_handshake, socket = _Socket, transport_cb = _Transport} } = State0) -> + ServerPrivateKey = get_server_private_key(KeyShare), + {Ref,Maybe} = maybe(), try @@ -536,7 +543,7 @@ 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), @@ -596,7 +603,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> end. -do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> +do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, + #state{static_env = #static_env{role = server}} = State0) -> {Ref,Maybe} = maybe(), try Maybe(verify_signature_algorithm(State0, CertificateVerify)), @@ -608,9 +616,28 @@ do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State}; {Ref, {{handshake_failure, Reason}, State}} -> {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State} + end; +do_wait_cv(#certificate_verify_1_3{} = _CertificateVerify, + #state{static_env = #static_env{role = client}} = State0) -> + {Ref,_Maybe} = maybe(), + try + %% TODO: implement validation + %% Maybe(verify_signature_algorithm(State0, CertificateVerify)), + %% Maybe(verify_certificate_verify(State0, CertificateVerify)) + {State0, wait_finished} + catch + {Ref, {{bad_certificate, Reason}, State}} -> + {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State}; + {Ref, {badarg, State}} -> + {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State}; + {Ref, {{handshake_failure, Reason}, State}} -> + {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State} end. + + + do_wait_finished(#finished{verify_data = VerifyData}, #state{connection_states = _ConnectionStates0, session = #session{session_id = _SessionId, @@ -619,6 +646,7 @@ do_wait_finished(#finished{verify_data = VerifyData}, key_share = _KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, static_env = #static_env{ + role = server, cert_db = _CertDbHandle, cert_db_ref = _CertDbRef, socket = _Socket, @@ -636,12 +664,147 @@ do_wait_finished(#finished{verify_data = VerifyData}, ssl_record:step_encryption_state(State1) + catch + {Ref, decrypt_error} -> + ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error) + end; +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{ + role = client, + cert_db = _CertDbHandle, + cert_db_ref = _CertDbRef, + socket = _Socket, + transport_cb = _Transport} + } = State0) -> + {Ref,_Maybe} = maybe(), + + try + %% Maybe(validate_client_finished(State0, VerifyData)), + + %% Maybe send Certificate + CertificateVerify + + %% Send Finished + %% Create Finished + Finished = finished(State0), + + %% Encode Finished + State1 = tls_connection:queue_handshake(Finished, State0), + + %% Send first flight + {State2, _} = tls_connection:send_handshake_flight(State1), + + State3 = calculate_traffic_secrets(State2), + + %% Configure traffic keys + ssl_record:step_encryption_state(State3) + catch {Ref, decrypt_error} -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error) end. + +do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, + session_id = SessionId, + extensions = Extensions} = ServerHello, + #state{connection_states = _ConnectionStates0, + key_share = ClientKeyShare0, + ssl_options = #ssl_options{ciphers = _ClientCiphers, + signature_algs = _ClientSignAlgs, + supported_groups = _ClientGroups}, + session = #session{own_certificate = _Cert}} = State0) -> + ServerKeyShare0 = maps:get(key_share, Extensions, undefined), + ServerKeyShare = get_key_shares(ServerKeyShare0), + 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)), + + %% TODO: implement validation + %% Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + %% Maybe(validate_key_share(ServerKeyShare, ClientGroups)), + + %% 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, SelectedCipherSuite, ClientKeyShare0, SessionId, + SelectedGroup, todo, ServerPublicKey), + + State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1), + + State3 = ssl_record:step_encryption_state(State2), + + {State3, wait_ee} + + catch + {Ref, {new_state, State, StateName}} -> + {State, StateName}; + {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} = _EE, + #state{connection_states = _ConnectionStates0, + ssl_options = #ssl_options{ciphers = _ServerCiphers, + signature_algs = _ServerSignAlgs, + supported_groups = _ServerGroups0}, + session = #session{own_certificate = _Cert}} = State0) -> + {Ref,_Maybe} = maybe(), + try + {State0, 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 + %% TODO: implement validation + %% Maybe(process_server_certificate(Certificate, State0)) + {State0, wait_cv} + 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) + end. + + %% TODO: Remove this function! %% not_implemented(State, Reason) -> %% {error, {not_implemented, State, Reason}}. @@ -652,6 +815,25 @@ do_wait_finished(#finished{verify_data = VerifyData}, %% {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{cipher_suite = Random}, State0) -> + case Random =:= hello_retry_request_random() of + true -> + {error, State0, start}; + false -> + ok + end. + %% Recipients of Finished messages MUST verify that the contents are %% correct and if incorrect MUST terminate the connection with a @@ -861,7 +1043,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,9 +1056,8 @@ 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 @@ -899,10 +1080,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 +1097,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 = @@ -966,9 +1150,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) -> @@ -976,9 +1162,24 @@ update_pending_connection_states(#state{connection_states = ReadKey, ReadIV, ReadFinishedKey), 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 @@ -1071,13 +1272,20 @@ 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. +%% TODO: tested only with basic handshake! +get_handshake_context_client([_H|T]) -> + T. + %% If sent by a client, the signature algorithm used in the signature %% MUST be one of those present in the supported_signature_algorithms @@ -1197,6 +1405,27 @@ 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}. + %% get_client_public_key(Group, ClientShares) -> %% case lists:keysearch(Group, 2, ClientShares) of @@ -1331,7 +1560,9 @@ 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. maybe() -> Ref = erlang:make_ref(), -- cgit v1.2.3 From 071ce727a110b21e33da929d1f79de6d4d4a0133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 14 May 2019 13:37:35 +0200 Subject: ssl: Add tests for TLS 1.3 client --- lib/ssl/test/ssl_basic_SUITE.erl | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 20d9f28512..96fbec1a9f 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -249,7 +249,9 @@ tls13_test_group() -> tls13_finished_verify_data, tls13_1_RTT_handshake, tls13_basic_ssl_server_openssl_client, + tls13_basic_ssl_server_ssl_client, tls13_custom_groups_ssl_server_openssl_client, + tls13_custom_groups_ssl_server_ssl_client, tls13_hello_retry_request_ssl_server_openssl_client, tls13_client_auth_empty_cert_alert_ssl_server_openssl_client, tls13_client_auth_empty_cert_ssl_server_openssl_client, @@ -5327,6 +5329,38 @@ tls13_basic_ssl_server_openssl_client(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). +tls13_basic_ssl_server_ssl_client() -> + [{doc,"Test TLS 1.3 basic connection between ssl server and ssl client"}]. + +tls13_basic_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_custom_groups_ssl_server_openssl_client() -> [{doc,"Test that ssl server can select a common group for key-exchange"}]. @@ -5351,6 +5385,39 @@ tls13_custom_groups_ssl_server_openssl_client(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). + +tls13_custom_groups_ssl_server_ssl_client() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +tls13_custom_groups_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], + ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1], + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_hello_retry_request_ssl_server_openssl_client() -> [{doc,"Test that ssl server can request a new group when the client's first key share" "is not supported"}]. -- cgit v1.2.3 From 81e0b78c0cd645c3864b103599d893fa4382864e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 14 May 2019 14:43:42 +0200 Subject: ssl: Validate selected cipher suite and key_share Implement validation of selected cipher suite and key_share in state 'wait_sh'. --- lib/ssl/src/tls_handshake_1_3.erl | 44 ++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index d7f45d984a..cceea3844e 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -465,7 +465,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% 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), @@ -716,10 +716,11 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, extensions = Extensions} = ServerHello, #state{connection_states = _ConnectionStates0, key_share = ClientKeyShare0, - ssl_options = #ssl_options{ciphers = _ClientCiphers, + ssl_options = #ssl_options{ciphers = ClientCiphers, signature_algs = _ClientSignAlgs, - supported_groups = _ClientGroups}, + supported_groups = ClientGroups0}, session = #session{own_certificate = _Cert}} = State0) -> + ClientGroups = get_supported_groups(ClientGroups0), ServerKeyShare0 = maps:get(key_share, Extensions, undefined), ServerKeyShare = get_key_shares(ServerKeyShare0), ClientKeyShare = get_key_shares(ClientKeyShare0), @@ -729,9 +730,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, %% Go to state 'start' if server replies with 'HelloRetryRequest'. Maybe(maybe_hello_retry_request(ServerHello, State0)), - %% TODO: implement validation - %% Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), - %% Maybe(validate_key_share(ServerKeyShare, ClientGroups)), + Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)), %% Get server public key {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare), @@ -1380,14 +1380,21 @@ 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). get_client_public_key([Group|_] = Groups, ClientShares) -> @@ -1452,6 +1459,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 -- cgit v1.2.3 From 39f7c8557ce813c1e908bdaeba5d9913b88d8277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Mon, 20 May 2019 16:28:31 +0200 Subject: ssl: Implement peer verification --- lib/ssl/src/tls_connection_1_3.erl | 10 +- lib/ssl/src/tls_handshake_1_3.erl | 363 +++++++++++++++++++++++-------------- lib/ssl/test/ssl_basic_SUITE.erl | 205 ++++++++++++++++++++- 3 files changed, 437 insertions(+), 141 deletions(-) diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index a6b589236f..41e1b671de 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -227,7 +227,15 @@ wait_cert_cr(internal, #change_cipher_spec{}, State0, _Module) -> wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) -> case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cv, State0); + ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); + {State1, NextState} -> + {Record, State} = tls_connection:next_record(State1), + tls_connection:next_event(NextState, Record, State) + 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} -> {Record, State} = tls_connection:next_record(State1), tls_connection:next_event(NextState, Record, State) diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index cceea3844e..3992e59878 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -117,7 +117,7 @@ add_signature_algorithms_cert(Extensions, undefined) -> Extensions; add_signature_algorithms_cert(Extensions, SignAlgsCert) -> Extensions#{signature_algorithms_cert => - #signature_algorithms{signature_scheme_list = SignAlgsCert}}. + #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}. filter_tls13_algs(undefined) -> undefined; @@ -125,7 +125,6 @@ filter_tls13_algs(Algo) -> lists:filter(fun is_atom/1, Algo). -%% TODO: use maybe monad for error handling! %% enum { %% X509(0), %% RawPublicKey(2), @@ -148,18 +147,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. @@ -167,7 +176,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, @@ -179,11 +188,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, @@ -454,8 +463,6 @@ do_start(#client_hello{cipher_suites = ClientCiphers, 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 @@ -557,7 +564,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), @@ -581,14 +588,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}; @@ -603,28 +612,11 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> end. -do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, - #state{static_env = #static_env{role = server}} = 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)) - catch - {Ref, {{bad_certificate, Reason}, State}} -> - {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State}; - {Ref, {badarg, State}} -> - {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State}; - {Ref, {{handshake_failure, Reason}, State}} -> - {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State} - end; -do_wait_cv(#certificate_verify_1_3{} = _CertificateVerify, - #state{static_env = #static_env{role = client}} = State0) -> - {Ref,_Maybe} = maybe(), - try - %% TODO: implement validation - %% Maybe(verify_signature_algorithm(State0, CertificateVerify)), - %% Maybe(verify_certificate_verify(State0, CertificateVerify)) - {State0, wait_finished} + 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}; @@ -634,24 +626,9 @@ do_wait_cv(#certificate_verify_1_3{} = _CertificateVerify, {?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{ - role = server, - cert_db = _CertDbHandle, - cert_db_ref = _CertDbRef, - socket = _Socket, - transport_cb = _Transport} - } = State0) -> + #state{static_env = #static_env{role = server}} = State0) -> {Ref,Maybe} = maybe(), @@ -668,58 +645,47 @@ do_wait_finished(#finished{verify_data = VerifyData}, {Ref, decrypt_error} -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error) end; +%% TLS Client 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{ - role = client, - cert_db = _CertDbHandle, - cert_db_ref = _CertDbRef, - socket = _Socket, - transport_cb = _Transport} - } = State0) -> - {Ref,_Maybe} = maybe(), + #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)), - %% Send Finished - %% Create Finished - Finished = finished(State0), + Finished = finished(State1), %% Encode Finished - State1 = tls_connection:queue_handshake(Finished, State0), + State2 = tls_connection:queue_handshake(Finished, State1), %% Send first flight - {State2, _} = tls_connection:send_handshake_flight(State1), + {State3, _} = tls_connection:send_handshake_flight(State2), - State3 = calculate_traffic_secrets(State2), + State4 = calculate_traffic_secrets(State3), %% Configure traffic keys - ssl_record:step_encryption_state(State3) + ssl_record:step_encryption_state(State4) catch {Ref, decrypt_error} -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, 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{connection_states = _ConnectionStates0, - key_share = ClientKeyShare0, + #state{key_share = ClientKeyShare0, ssl_options = #ssl_options{ciphers = ClientCiphers, - signature_algs = _ClientSignAlgs, - supported_groups = ClientGroups0}, - session = #session{own_certificate = _Cert}} = State0) -> + supported_groups = ClientGroups0}} = State0) -> ClientGroups = get_supported_groups(ClientGroups0), ServerKeyShare0 = maps:get(key_share, Extensions, undefined), ServerKeyShare = get_key_shares(ServerKeyShare0), @@ -740,7 +706,7 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, %% Update state State1 = update_start_state(State0, SelectedCipherSuite, ClientKeyShare0, SessionId, - SelectedGroup, todo, ServerPublicKey), + SelectedGroup, undefined, ServerPublicKey), State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1), @@ -764,13 +730,10 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, end. -do_wait_ee(#encrypted_extensions{extensions = _Extensions} = _EE, - #state{connection_states = _ConnectionStates0, - ssl_options = #ssl_options{ciphers = _ServerCiphers, - signature_algs = _ServerSignAlgs, - supported_groups = _ServerGroups0}, - session = #session{own_certificate = _Cert}} = State0) -> +do_wait_ee(#encrypted_extensions{extensions = _Extensions}, State0) -> + {Ref,_Maybe} = maybe(), + try {State0, wait_cert_cr} catch @@ -787,17 +750,31 @@ do_wait_ee(#encrypted_extensions{extensions = _Extensions} = _EE, end. -do_wait_cert_cr(#certificate_1_3{} = _Certificate, State0) -> - {Ref,_Maybe} = maybe(), +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) + end; +do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> + {Ref,Maybe} = maybe(), try - %% TODO: implement validation - %% Maybe(process_server_certificate(Certificate, State0)) - {State0, wait_cv} + 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}} -> @@ -805,6 +782,7 @@ do_wait_cert_cr(#certificate_1_3{} = _Certificate, State0) -> end. + %% TODO: Remove this function! %% not_implemented(State, Reason) -> %% {error, {not_implemented, State, Reason}}. @@ -835,6 +813,64 @@ maybe_hello_retry_request(#server_hello{cipher_suite = Random}, State0) -> end. +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, + sign_alg = SignatureScheme}, + ssl_options = #ssl_options{} = _SslOpts, + key_share = _KeyShare, + handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, + connection_env = #connection_env{private_key = CertPrivateKey}, + 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 %% "decrypt_error" alert. @@ -866,7 +902,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}}; @@ -885,19 +921,48 @@ 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{ + extensions = Extensions}, + #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)), + + %% Validate CertificateRequest + + %% Validate if client certificate has a signature algorithm supported by the server. + {_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 @@ -906,19 +971,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 @@ -1282,22 +1346,31 @@ get_handshake_context_server([H|T]) when is_binary(H) -> get_handshake_context_server(L) -> L. -%% TODO: tested only with basic handshake! -get_handshake_context_client([_H|T]) -> - T. + +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), @@ -1306,11 +1379,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}) -> @@ -1330,10 +1411,11 @@ verify_certificate_verify(#state{connection_states = ConnectionStates, 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, PublicKey) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> @@ -1347,6 +1429,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 diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 96fbec1a9f..067f9ec1e6 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -253,14 +253,20 @@ tls13_test_group() -> tls13_custom_groups_ssl_server_openssl_client, tls13_custom_groups_ssl_server_ssl_client, tls13_hello_retry_request_ssl_server_openssl_client, + %% TODO: tls13_hello_retry_request_ssl_server_ssl_client, tls13_client_auth_empty_cert_alert_ssl_server_openssl_client, + tls13_client_auth_empty_cert_alert_ssl_server_ssl_client, tls13_client_auth_empty_cert_ssl_server_openssl_client, + tls13_client_auth_empty_cert_ssl_server_ssl_client, tls13_client_auth_ssl_server_openssl_client, + tls13_client_auth_ssl_server_ssl_client, tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client, tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client, tls13_hrr_client_auth_ssl_server_openssl_client, tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client, + tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client, tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client, + tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client, tls13_connection_information]. %%-------------------------------------------------------------------- @@ -5341,8 +5347,6 @@ tls13_basic_ssl_server_ssl_client(Config) -> ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, @@ -5475,6 +5479,45 @@ tls13_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). + +tls13_client_auth_empty_cert_alert_ssl_server_ssl_client() -> + [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +tls13_client_auth_empty_cert_alert_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts2], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, + {error, + {tls_alert, + {certificate_required, + "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_client_auth_empty_cert_ssl_server_openssl_client() -> [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. @@ -5504,13 +5547,47 @@ tls13_client_auth_empty_cert_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_client_auth_empty_cert_ssl_server_ssl_client() -> + [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. + +tls13_client_auth_empty_cert_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts2], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_client_auth_ssl_server_openssl_client() -> [{doc,"TLS 1.3: Test client authentication."}]. tls13_client_auth_ssl_server_openssl_client(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer}, @@ -5530,6 +5607,38 @@ tls13_client_auth_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_client_auth_ssl_server_ssl_client() -> + [{doc,"TLS 1.3: Test client authentication."}]. + +tls13_client_auth_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + %%Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client() -> [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. @@ -5658,9 +5767,49 @@ tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). -%% Triggers Client Alert as openssl s_client does not have a certificate with a +tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result( + Server, + {error, + {tls_alert, + {insufficient_security, + "received SERVER ALERT: Fatal - Insufficient Security - " + "\"No suitable signature algorithm\""}}}), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + +%% Triggers a Server Alert as openssl s_client does not have a certificate with a %% signature algorithm supported by the server (signature_algorithms_cert extension %% of CertificateRequest does not contain the algorithm of the client certificate). +%% openssl s_client sends an empty certificate. tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client() -> [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. @@ -5690,8 +5839,52 @@ tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client(Config) - Server, {error, {tls_alert, - {illegal_parameter, - "received CLIENT ALERT: Fatal - Illegal Parameter"}}}), + {certificate_required, + "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {log_level, debug}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result( + Server, + {error, + {tls_alert, + {certificate_required, + "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). -- cgit v1.2.3 From e69cc5de116420ee861dab9dca1481a2f32909d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 21 May 2019 13:43:57 +0200 Subject: ssl: Remove calls to tls_connection:next_record/1 --- lib/ssl/src/tls_connection_1_3.erl | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 41e1b671de..d1b004975c 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -188,9 +188,8 @@ wait_finished(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -wait_sh(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +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 -> @@ -199,46 +198,40 @@ wait_sh(internal, #server_hello{} = Hello, State0, _Module) -> %% TODO: Implement hello_retry_request {next_state, start, State1, []}; {State1, wait_ee} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(wait_ee, Record, State) + 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{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +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} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(NextState, Record, State) + 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{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +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 -> ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); {State1, NextState} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(NextState, Record, State) + 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} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(NextState, Record, State) + 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). -- cgit v1.2.3 From 8c55d3f17a88a919df1a86430b59f6d8fe816fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 21 May 2019 15:46:41 +0200 Subject: ssl: Generate only one key_share entry (client) Change default behavior to only send key_share entry for the most preferred group in ClientHello. --- lib/ssl/src/tls_connection.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 9c8c3b9352..aec58a27eb 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -1293,9 +1293,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. -- cgit v1.2.3 From a4226b1fd9093a43902510b6e7b0e7af2406a23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 21 May 2019 16:57:37 +0200 Subject: ssl: Implement hello_retry_request (client) --- lib/ssl/src/ssl_handshake.erl | 5 ++ lib/ssl/src/tls_connection_1_3.erl | 13 +++- lib/ssl/src/tls_handshake_1_3.erl | 156 ++++++++++++++++++++++++++++++------- lib/ssl/test/ssl_basic_SUITE.erl | 146 +++++++++++++++++++++++++++++++++- 4 files changed, 289 insertions(+), 31 deletions(-) diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index f68d3e9b26..b51ba0fa2d 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -3046,6 +3046,11 @@ 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(_, server_hello) -> #{renegotiation_info => undefined, alpn => undefined, diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index d1b004975c..821b7000cc 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -130,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). @@ -194,9 +201,9 @@ 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} -> - %% TODO: Implement hello_retry_request - {next_state, start, State1, []}; + {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; diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 3992e59878..12ab2015aa 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -51,6 +51,13 @@ 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 %%==================================================================== @@ -82,10 +89,7 @@ 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, _) -> - hello_retry_request_random(). - -hello_retry_request_random() -> - crypto:hash(sha256, "HelloRetryRequest"). + ?HELLO_RETRY_REQUEST_RANDOM. %% TODO: implement support for encrypted_extensions @@ -267,6 +271,21 @@ encode_handshake(HandshakeMsg) -> %% Decode handshake %%==================================================================== + +decode_handshake(?SERVER_HELLO, <>) + 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, <>) -> Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ @@ -443,6 +462,7 @@ build_content(Context, THash) -> %%==================================================================== +%% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, extensions = Extensions} = _Hello, @@ -518,6 +538,81 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ?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; +%% 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, SelectedCipherSuite, ClientKeyShare, SessionId, + SelectedGroup, undefined, undefined), + + %% 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. @@ -688,7 +783,6 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, supported_groups = ClientGroups0}} = State0) -> ClientGroups = get_supported_groups(ClientGroups0), ServerKeyShare0 = maps:get(key_share, Extensions, undefined), - ServerKeyShare = get_key_shares(ServerKeyShare0), ClientKeyShare = get_key_shares(ClientKeyShare0), {Ref,Maybe} = maybe(), @@ -696,6 +790,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, %% 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)), @@ -715,8 +811,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, {State3, wait_ee} catch - {Ref, {new_state, State, StateName}} -> - {State, StateName}; + {Ref, {State, StateName, ServerHello}} -> + {State, StateName, ServerHello}; {Ref, {insufficient_security, no_suitable_groups}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> @@ -786,7 +882,7 @@ do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> %% 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), @@ -804,25 +900,20 @@ do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> %% 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{cipher_suite = Random}, State0) -> - case Random =:= hello_retry_request_random() of - true -> - {error, State0, start}; - false -> - ok - end. +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, - sign_alg = SignatureScheme}, + own_certificate = OwnCert}, ssl_options = #ssl_options{} = _SslOpts, key_share = _KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, - connection_env = #connection_env{private_key = CertPrivateKey}, static_env = #static_env{ role = client, cert_db = CertDbHandle, @@ -867,10 +958,6 @@ maybe_queue_cert_verify(_Certificate, end. - - - - %% Recipients of Finished messages MUST verify that the contents are %% correct and if incorrect MUST terminate the connection with a %% "decrypt_error" alert. @@ -921,8 +1008,7 @@ maybe_send_certificate_request(State, #ssl_options{ {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. -process_certificate_request(#certificate_request_1_3{ - extensions = Extensions}, +process_certificate_request(#certificate_request_1_3{}, #state{session = #session{own_certificate = undefined}} = State) -> {ok, {State#state{client_certificate_requested = true}, wait_cert}}; @@ -934,9 +1020,6 @@ process_certificate_request(#certificate_request_1_3{ ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - %% Validate CertificateRequest - - %% Validate if client certificate has a signature algorithm supported by the server. {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), %% Check if server supports signature algorithm of client certificate @@ -1126,6 +1209,7 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, %% Calculate [sender]_handshake_traffic_secret {Messages, _} = HHistory, + ClientHSTrafficSecret = tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), ServerHSTrafficSecret = @@ -1492,6 +1576,21 @@ 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) -> get_client_public_key(Groups, ClientShares, Group). %% @@ -1679,6 +1778,9 @@ get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> ServerShare. +get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) -> + SelectedGroup. + maybe() -> Ref = erlang:make_ref(), Ok = fun(ok) -> ok; diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 067f9ec1e6..86554f3671 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -253,7 +253,7 @@ tls13_test_group() -> tls13_custom_groups_ssl_server_openssl_client, tls13_custom_groups_ssl_server_ssl_client, tls13_hello_retry_request_ssl_server_openssl_client, - %% TODO: tls13_hello_retry_request_ssl_server_ssl_client, + tls13_hello_retry_request_ssl_server_ssl_client, tls13_client_auth_empty_cert_alert_ssl_server_openssl_client, tls13_client_auth_empty_cert_alert_ssl_server_ssl_client, tls13_client_auth_empty_cert_ssl_server_openssl_client, @@ -261,8 +261,11 @@ tls13_test_group() -> tls13_client_auth_ssl_server_openssl_client, tls13_client_auth_ssl_server_ssl_client, tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client, + tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client, tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client, + tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client, tls13_hrr_client_auth_ssl_server_openssl_client, + tls13_hrr_client_auth_ssl_server_ssl_client, tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client, tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client, tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client, @@ -5447,6 +5450,38 @@ tls13_hello_retry_request_ssl_server_openssl_client(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). + +tls13_hello_retry_request_ssl_server_ssl_client() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +tls13_hello_retry_request_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + tls13_client_auth_empty_cert_alert_ssl_server_openssl_client() -> [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. @@ -5674,6 +5709,46 @@ tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, + {error, + {tls_alert, + {certificate_required, + "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client() -> [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. @@ -5705,6 +5780,42 @@ tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. + +tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_hrr_client_auth_ssl_server_openssl_client() -> [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}]. @@ -5733,6 +5844,39 @@ tls13_hrr_client_auth_ssl_server_openssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_hrr_client_auth_ssl_server_ssl_client() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +tls13_hrr_client_auth_ssl_server_ssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + + tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client() -> [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. -- cgit v1.2.3 From 7fe0e9f5ccf197e3e3d93db82713eb9948795a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 23 May 2019 14:25:42 +0200 Subject: ssl: Improve interoperability with openssl Handle the NewSessionTicket post-handshake message that is sent by openssl s_server after a successful handshake. --- lib/ssl/src/ssl_logger.erl | 5 +++++ lib/ssl/src/tls_connection.erl | 6 ++++++ 2 files changed, 11 insertions(+) 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 aec58a27eb..2651fc09bd 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"). @@ -810,6 +811,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). -- cgit v1.2.3 From de16b5e94d8706d48a59ed45333cac15db23772c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 23 May 2019 15:31:23 +0200 Subject: ssl: Add openssl interop test for TLS 1.3 client --- lib/ssl/test/ssl_basic_SUITE.erl | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 86554f3671..8cb98e7fa6 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -250,6 +250,7 @@ tls13_test_group() -> tls13_1_RTT_handshake, tls13_basic_ssl_server_openssl_client, tls13_basic_ssl_server_ssl_client, + tls13_basic_openssl_server_ssl_client, tls13_custom_groups_ssl_server_openssl_client, tls13_custom_groups_ssl_server_ssl_client, tls13_hello_retry_request_ssl_server_openssl_client, @@ -5368,6 +5369,50 @@ tls13_basic_ssl_server_ssl_client(Config) -> ssl_test_lib:close_port(Client). +tls13_basic_openssl_server_ssl_client() -> + [{doc,"Test TLS 1.3 basic connection between openssl server and ssl client"}]. + +tls13_basic_openssl_server_ssl_client(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + "-tls1_3", + "-cert", CertFile, "-CAfile", CaCertFile, + "-key", KeyFile, "-Verify", "2"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, tls), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + + tls13_custom_groups_ssl_server_openssl_client() -> [{doc,"Test that ssl server can select a common group for key-exchange"}]. -- cgit v1.2.3 From f984402cef930d31182873d3ff6cc6b3cda17f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 23 May 2019 16:46:17 +0200 Subject: ssl: Update standards compliance --- lib/ssl/doc/src/standards_compliance.xml | 357 ++++++++++++++++--------------- 1 file changed, 182 insertions(+), 175 deletions(-) diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index ca98385f85..650fcec920 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -126,7 +126,7 @@
TLS 1.3 -

OTP-22 introduces basic support for TLS 1.3 on the server side. Basic functionality +

OTP-22 introduces basic support for TLS 1.3. Basic functionality covers a simple TLS 1.3 handshake with support of the mandatory extensions (supported_groups, signature_algorithms, key_share, supported_versions and signature_algorithms_cert). The server supports a selective set of cryptographic algorithms:

@@ -147,8 +147,6 @@

For more detailed information see the Standards Compliance below.

-

Note that the client side is not yet functional. It is planned to be released - later in OTP-22.

The following table describes the current state of standards compliance for TLS 1.3.

(C = Compliant, NC = Non-Compliant, PC = Partially-Compliant, @@ -176,25 +174,25 @@ Version downgrade protection mechanism C - 22 + 22 RSASSA-PSS signature schemes PC - 22 + 22 supported_versions (ClientHello) extension C - 22 + 22 signature_algorithms_cert extension C - 22 + 22 @@ -211,7 +209,7 @@ (EC)DHE C - 22 + 22 @@ -295,8 +293,8 @@ Client - NC - + PC + 22.1 @@ -319,14 +317,14 @@ supported_groups (RFC7919) - NC - + C + 22.1 signature_algorithms (RFC8446) - NC - + C + 22.1 @@ -373,8 +371,8 @@ key_share (RFC8446) - NC - + C + 22.1 @@ -403,8 +401,8 @@ supported_versions (RFC8446) - NC - + C + 22.1 @@ -427,8 +425,8 @@ signature_algorithms_cert (RFC8446) - NC - + C + 22.1 @@ -459,13 +457,13 @@ supported_groups (RFC7919) C - 22 + 22 signature_algorithms (RFC8446) C - 22 + 22 @@ -513,7 +511,7 @@ key_share (RFC8446) C - 22 + 22 @@ -543,7 +541,7 @@ supported_versions (RFC8446) C - 22 + 22 @@ -567,7 +565,7 @@ signature_algorithms_cert (RFC8446) C - 22 + 22 @@ -577,20 +575,20 @@ Client - NC - + PC + 22.1 Version downgrade protection - NC - + C + 22.1 key_share (RFC8446) - NC - + C + 22.1 @@ -601,8 +599,8 @@ supported_versions (RFC8446) - NC - + C + 22.1 @@ -615,13 +613,13 @@ Version downgrade protection C - 22 + 22 key_share (RFC8446) C - 22 + 22 @@ -633,7 +631,7 @@ supported_versions (RFC8446) C - 22 + 22 @@ -650,7 +648,7 @@ key_share (RFC8446) C - 22 + 22 @@ -662,7 +660,7 @@ supported_versions (RFC8446) C - 22 + 22 @@ -672,8 +670,8 @@ Client - NC - + C + 22.1 @@ -706,26 +704,26 @@ Client - NC - + PC + 22.1 rsa_pkcs1_sha256 - NC - + C + 22.1 rsa_pkcs1_sha384 - NC - + C + 22.1 rsa_pkcs1_sha512 - NC - + C + 22.1 @@ -748,20 +746,20 @@ rsa_pss_rsae_sha256 - NC - + C + 22.1 rsa_pss_rsae_sha384 - NC - + C + 22.1 rsa_pss_rsae_sha512 - NC - + C + 22.1 @@ -796,14 +794,14 @@ rsa_pkcs1_sha1 - NC - + C + 22.1 ecdsa_sha1 - NC - + C + 22.1 @@ -816,19 +814,19 @@ rsa_pkcs1_sha256 C - 22 + 22 rsa_pkcs1_sha384 C - 22 + 22 rsa_pkcs1_sha512 C - 22 + 22 @@ -852,19 +850,19 @@ rsa_pss_rsae_sha256 C - 22 + 22 rsa_pss_rsae_sha384 C - 22 + 22 rsa_pss_rsae_sha512 C - 22 + 22 @@ -900,13 +898,13 @@ rsa_pkcs1_sha1 C - 22 + 22 ecdsa_sha1 C - 22 + 22 @@ -973,62 +971,62 @@ secp256r1 - NC - + C + 22.1 secp384r1 - NC - + C + 22.1 secp521r1 - NC - + C + 22.1 x25519 - NC - + C + 22.1 x448 - NC - + C + 22.1 ffdhe2048 - NC - + C + 22.1 ffdhe3072 - NC - + C + 22.1 ffdhe4096 - NC - + C + 22.1 ffdhe6144 - NC - + C + 22.1 ffdhe8192 - NC - + C + 22.1 @@ -1105,8 +1103,8 @@ Client - NC - + C + 22.1 @@ -1224,8 +1222,8 @@ Client - NC - + PC + 22.1 @@ -1362,8 +1360,8 @@ Client - NC - + PC + 22.1 @@ -1374,8 +1372,8 @@ signature_algorithms (RFC8446) - NC - + C + 22.1 @@ -1398,8 +1396,8 @@ signature_algorithms_cert (RFC8446) - NC - + C + 22.1 @@ -1417,8 +1415,8 @@ signature_algorithms (RFC8446) - NC - + C + 22 @@ -1441,8 +1439,8 @@ signature_algorithms_cert (RFC8446) - NC - + C + 22 @@ -1463,8 +1461,8 @@ Client - NC - + PC + 22.1 @@ -1521,73 +1519,82 @@ 4.4.2.2. Server Certificate Selection - Client - NC - + + PC + 22 - certificate type MUST be X.509v3 - NC - + The certificate type MUST be X.509v3, unless explicitly + negotiated otherwise + C + 22 - certificate's public key is compatible - NC - + The server's end-entity certificate's public key (and associated + restrictions) MUST be compatible with the selected authentication + algorithm from the client's "signature_algorithms" extension + (currently RSA, ECDSA, or EdDSA). + C + 22 - The certificate MUST allow the key to be used for signing - NC - + The certificate MUST allow the key to be used for signing + with a signature scheme indicated in the client's "signature_algorithms"/"signature_algorithms_cert" + extensions + C + 22 - server_name and certificate_authorities are used + The "server_name" and "certificate_authorities" + extensions are used to guide certificate selection. As servers + MAY require the presence of the "server_name" extension, clients + SHOULD send this extension, when applicable. NC - + - - Server + + + 4.4.2.3. Client Certificate Selection + + + PC - + 22.1 - certificate type MUST be X.509v3 + The certificate type MUST be X.509v3, unless explicitly + negotiated otherwise C - 22 + 22.1 - certificate's public key is compatible - C - 22 + If the "certificate_authorities" extension in the + CertificateRequest message was present, at least one of the + certificates in the certificate chain SHOULD be issued by one of + the listed CAs. + NC + - The certificate MUST allow the key to be used for signing + The certificates MUST be signed using an acceptable signature + algorithm C - 22 + 22.1 - server_name and certificate_authorities are used - NC - - - - - - - 4.4.2.3. Client Certificate Selection - - - + If the CertificateRequest message contained a non-empty + "oid_filters" extension, the end-entity certificate MUST match the + extension OIDs that are recognized by the client NC @@ -1599,8 +1606,8 @@ Client - NC - + C + 22.1 @@ -1616,8 +1623,8 @@ Client - NC - + C + 22.1 @@ -1633,8 +1640,8 @@ Client - NC - + C + 22.1 @@ -1738,25 +1745,25 @@ MUST NOT be interleaved with other record types C - 22 + 22 MUST NOT span key changes C - 22 + 22 MUST NOT send zero-length fragments C - 22 + 22 Alert messages MUST NOT be fragmented C - 22 + 22 @@ -1807,7 +1814,7 @@ The padding sent is automatically verified C - 22 + 22 @@ -1957,19 +1964,19 @@ MUST implement the TLS_AES_128_GCM_SHA256 C - 22 + 22 SHOULD implement the TLS_AES_256_GCM_SHA384 C - 22 + 22 SHOULD implement the TLS_CHACHA20_POLY1305_SHA256 C - 22 + 22 @@ -1982,13 +1989,13 @@ MUST support rsa_pkcs1_sha256 (for certificates) C - 22 + 22 MUST support rsa_pss_rsae_sha256 (for CertificateVerify and certificates) C - 22 + 22 @@ -2007,13 +2014,13 @@ MUST support key exchange with secp256r1 C - 22 + 22 SHOULD support key exchange with X25519 C - 22 + 22 @@ -2030,7 +2037,7 @@ Supported Versions C - 22 + 22 @@ -2042,25 +2049,25 @@ Signature Algorithms C - 22 + 22 Signature Algorithms Certificate C - 22 + 22 Negotiated Groups C - 22 + 22 Key Share C - 22 + 22 @@ -2072,32 +2079,32 @@ MUST send and use these extensions - C + PC 22 "supported_versions" is REQUIRED for ClientHello, ServerHello and HelloRetryRequest - PC - 22 + C + 22.1 "signature_algorithms" is REQUIRED for certificate authentication C - 22 + 22 "supported_groups" is REQUIRED for ClientHello messages using (EC)DHE key exchange C - 22 + 22 "key_share" is REQUIRED for (EC)DHE key exchange C - 22 + 22 @@ -2115,8 +2122,8 @@ TLS 1.3 ClientHello - NC - + PC + 22.1 @@ -2127,8 +2134,8 @@ If containing a "supported_groups" extension, it MUST also contain a "key_share" extension, and vice versa. An empty KeyShare.client_shares vector is permitted. - NC - + C + 22.1 @@ -2193,25 +2200,25 @@ TLS_AES_128_GCM_SHA256 C - 22 + 22 TLS_AES_256_GCM_SHA384 C - 22 + 22 TLS_CHACHA20_POLY1305_SHA256 C - 22 + 22 TLS_AES_128_CCM_SHA256 C - 22 + 22 -- cgit v1.2.3 From a816848aaa259d7d2ee58ec2518220a046d3044d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Fri, 24 May 2019 11:33:53 +0200 Subject: ssl: Improve standards compliance document OTP-15431 --- lib/ssl/doc/src/standards_compliance.xml | 45 ++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index 650fcec920..3bd86178c8 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -129,7 +129,7 @@

OTP-22 introduces basic support for TLS 1.3. Basic functionality covers a simple TLS 1.3 handshake with support of the mandatory extensions (supported_groups, signature_algorithms, key_share, supported_versions and - signature_algorithms_cert). The server supports a selective set of cryptographic algorithms:

+ signature_algorithms_cert). The current implementation supports a selective set of cryptographic algorithms:

Key Exchange: ECDHE Groups: all standard groups supported for the Diffie-Hellman key exchange @@ -140,7 +140,6 @@

Other notable features:

- The server supports the HelloRetryRequest mechanism PSK and session resumption not supported Early data and 0-RTT not supported Key and Initialization Vector Update not supported @@ -965,8 +964,8 @@ Client - NC - + C + 22.1 @@ -2122,14 +2121,14 @@ TLS 1.3 ClientHello - PC + C 22.1 If not containing a "pre_shared_key" extension, it MUST contain both a "signature_algorithms" extension and a "supported_groups" extension. - NC - + C + 22.1 @@ -2158,30 +2157,44 @@ - NC - + C + 22.1 MUST correctly handle extensible fields - NC - + C + 22.1 - A client sending a ClientHello MUST support all parameters advertised in it. - NC - + A client sending a ClientHello MUST support all parameters + advertised in it. Otherwise, the server may fail to interoperate by selecting one of those parameters. + C + 22.1 - A middlebox which terminates a TLS connection MUST behave as a compliant TLS server + A server receiving a ClientHello MUST correctly ignore all + unrecognized cipher suites, extensions, and other parameters. Otherwise, it may fail to + interoperate with newer clients. In TLS 1.3, a client receiving a CertificateRequest or + NewSessionTicket MUST also ignore all unrecognized extensions. + C + 22.1 + + + + + A middlebox which terminates a TLS connection MUST behave as a + compliant TLS server NA - A middlebox which forwards ClientHello parameters it does not understand MUST NOT process any messages beyond that ClientHello. + A middlebox which forwards ClientHello parameters it does not + understand MUST NOT process any messages beyond that ClientHello. It MUST forward all subsequent + traffic unmodified. Otherwise, it may fail to interoperate with newer clients and servers. NA -- cgit v1.2.3