diff options
Diffstat (limited to 'lib/ssl/src')
-rw-r--r-- | lib/ssl/src/Makefile | 142 | ||||
-rw-r--r-- | lib/ssl/src/dtls_connection.erl | 128 | ||||
-rw-r--r-- | lib/ssl/src/dtls_handshake.erl | 2 | ||||
-rw-r--r-- | lib/ssl/src/ssl.erl | 39 | ||||
-rw-r--r-- | lib/ssl/src/ssl_cipher.erl | 56 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 251 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.hrl | 3 | ||||
-rw-r--r-- | lib/ssl/src/ssl_dh_groups.erl | 26 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 327 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.hrl | 3 | ||||
-rw-r--r-- | lib/ssl/src/ssl_internal.hrl | 1 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection.erl | 348 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection_1_3.erl | 131 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake.erl | 15 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 344 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.hrl | 2 | ||||
-rw-r--r-- | lib/ssl/src/tls_record.erl | 37 | ||||
-rw-r--r-- | lib/ssl/src/tls_record_1_3.erl | 39 | ||||
-rw-r--r-- | lib/ssl/src/tls_sender.erl | 94 | ||||
-rw-r--r-- | lib/ssl/src/tls_v1.erl | 17 |
20 files changed, 1334 insertions, 671 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index c7dee81c71..8dc76f2638 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -39,66 +39,80 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN) # ---------------------------------------------------- BEHAVIOUR_MODULES= \ - ssl_session_cache_api \ - ssl_crl_cache_api + ssl_crl_cache_api \ + ssl_session_cache_api + MODULES= \ - ssl \ - ssl_alert \ - ssl_app \ - ssl_sup \ - ssl_admin_sup\ - tls_connection_sup \ - ssl_connection_sup \ - ssl_listen_tracker_sup\ + dtls_connection \ dtls_connection_sup \ - dtls_packet_demux \ + dtls_handshake \ dtls_listener_sup \ - ssl_dist_sup\ - ssl_dist_admin_sup\ - ssl_dist_connection_sup\ + dtls_packet_demux \ + dtls_record \ + dtls_socket \ + dtls_v1 \ inet_tls_dist \ inet6_tls_dist \ - ssl_certificate\ - ssl_pkix_db\ + ssl \ + ssl_admin_sup \ + ssl_alert \ + ssl_app \ + ssl_certificate \ ssl_cipher \ ssl_cipher_format \ - ssl_srp_primes \ - tls_connection \ - tls_connection_1_3 \ - dtls_connection \ - tls_sender\ ssl_config \ ssl_connection \ - tls_handshake \ - tls_handshake_1_3\ - dtls_handshake\ - ssl_handshake\ - ssl_manager \ - ssl_session \ - ssl_session_cache \ - ssl_pem_cache \ - ssl_crl\ + ssl_connection_sup \ + ssl_crl \ ssl_crl_cache \ ssl_crl_hash_dir \ - tls_socket \ - dtls_socket \ - tls_record \ - tls_record_1_3\ - dtls_record \ + ssl_dh_groups \ + ssl_dist_admin_sup \ + ssl_dist_connection_sup \ + ssl_dist_sup \ + ssl_handshake \ + ssl_listen_tracker_sup \ + ssl_logger \ + ssl_manager \ + ssl_pem_cache \ + ssl_pkix_db \ ssl_record \ + ssl_session \ + ssl_session_cache \ + ssl_srp_primes \ + ssl_sup \ ssl_v3 \ - tls_v1 \ - dtls_v1 \ - ssl_logger \ - ssl_dh_groups + tls_connection \ + tls_connection_sup \ + tls_connection_1_3 \ + tls_handshake \ + tls_handshake_1_3 \ + tls_record \ + tls_record_1_3 \ + tls_sender \ + tls_socket \ + tls_v1 + INTERNAL_HRL_FILES = \ - ssl_alert.hrl ssl_cipher.hrl \ - tls_connection.hrl dtls_connection.hrl ssl_connection.hrl \ - ssl_handshake.hrl tls_handshake.hrl tls_handshake_1_3.hrl dtls_handshake.hrl \ - ssl_api.hrl ssl_internal.hrl \ - ssl_record.hrl tls_record.hrl tls_record_1_3.hrl dtls_record.hrl ssl_srp.hrl + dtls_connection.hrl \ + dtls_handshake.hrl \ + dtls_record.hrl \ + ssl_alert.hrl \ + ssl_api.hrl \ + ssl_cipher.hrl \ + ssl_connection.hrl \ + ssl_handshake.hrl \ + ssl_internal.hrl \ + ssl_record.hrl \ + ssl_srp.hrl \ + tls_connection.hrl \ + tls_handshake.hrl \ + tls_handshake_1_3.hrl \ + tls_record.hrl \ + tls_record_1_3.hrl + ERL_FILES= \ $(MODULES:%=%.erl) \ @@ -117,6 +131,10 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) +DEPDIR=$(ERL_TOP)/lib/ssl/src/deps +DEP_FILE=$(DEPDIR)/ssl.d +$(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null) + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- @@ -133,11 +151,22 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) -debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) +$(DEP_FILE): $(ERL_FILES) + $(gen_verbose)erlc -M $(ERL_FILES) \ + | sed "s@$(ERL_TOP)@../../..@g" \ + | sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \ + | sed 's@^dtls_@$$(EBIN)/dtls_@' \ + | sed 's@^inet_@$$(EBIN)/inet_@' \ + | sed 's@^ssl_@$$(EBIN)/ssl_@' \ + | sed 's@^tls_@$$(EBIN)/tls_@' \ + > $(DEP_FILE) + +debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE) clean: rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) rm -f errs core *~ + rm -rf $(DEPDIR) $(APP_TARGET): $(APP_SRC) ../vsn.mk $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ @@ -147,7 +176,6 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk docs: - # ---------------------------------------------------- # Release Target # ---------------------------------------------------- @@ -165,24 +193,4 @@ release_docs_spec: # ---------------------------------------------------- # Dependencies # ---------------------------------------------------- -$(EBIN)/inet_tls_dist.$(EMULATOR): ../../kernel/include/net_address.hrl ../../kernel/include/dist.hrl ../../kernel/include/dist_util.hrl -$(EBIN)/tls.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl -$(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl -$(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl -$(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl -$(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_connection.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_connection_1_3.$(EMULATOR): ssl_internal.hrl tls_connection.hrl -$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_connection.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl ssl_connection.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_handshake_1_3.$(EMULATOR): tls_handshake_1_3.hrl tls_handshake.hrl ssl_internal.hrl -$(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl -$(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl -$(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl -$(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl -$(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl -$(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl -$(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl -$(EBIN)/ssl_cache.$(EMULATOR): ssl_cache.erl ssl_internal.hrl ../../public_key/include/public_key.hrl - +-include $(DEP_FILE) diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 8ed4505256..73885de6c1 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -40,7 +40,7 @@ -export([start_fsm/8, start_link/7, init/1, pids/1]). %% State transition handling --export([next_record/1, next_event/3, next_event/4, handle_common_event/4]). +-export([next_event/3, next_event/4, handle_common_event/4]). %% Handshake handling -export([renegotiate/2, send_handshake/2, @@ -51,7 +51,7 @@ -export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, +-export([encode_data/3, next_record/1, send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions @@ -163,9 +163,9 @@ next_record(State) -> next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). -next_event(connection = StateName, no_record, +next_event(StateName, no_record, #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case next_record_if_active(State0) of + case next_record(State0) of {no_record, State} -> ssl_connection:hibernate_after(StateName, State, Actions); {#ssl_tls{epoch = CurrentEpoch, @@ -179,21 +179,18 @@ next_event(connection = StateName, no_record, {#ssl_tls{epoch = Epoch, type = ?HANDSHAKE, version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - {NextRecord, State} = next_record(State2), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake {#ssl_tls{epoch = Epoch, type = ?CHANGE_CIPHER_SPEC, version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - {NextRecord, State} = next_record(State2), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); {#ssl_tls{epoch = _Epoch, - version = _Version}, State1} -> + version = _Version}, State} -> %% TODO maybe buffer later epoch - {Record, State} = next_record(State1), - next_event(StateName, Record, State, Actions); + next_event(StateName, no_record, State, Actions); {#alert{} = Alert, State} -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; @@ -211,24 +208,20 @@ next_event(connection = StateName, Record, #ssl_tls{epoch = Epoch, type = ?HANDSHAKE, version = _Version} when Epoch == CurrentEpoch-1 -> - {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - {NextRecord, State} = next_record(State1), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake #ssl_tls{epoch = Epoch, type = ?CHANGE_CIPHER_SPEC, version = _Version} when Epoch == CurrentEpoch-1 -> - {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - {NextRecord, State} = next_record(State1), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); _ -> next_event(StateName, no_record, State0, Actions) end; next_event(StateName, Record, #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> case Record of - no_record -> - {next_state, StateName, State0, Actions}; #ssl_tls{epoch = CurrentEpoch, version = Version} = Record -> State = dtls_version(StateName, Version, State0), @@ -237,8 +230,7 @@ next_event(StateName, Record, #ssl_tls{epoch = _Epoch, version = _Version} = _Record -> %% TODO maybe buffer later epoch - {Record, State} = next_record(State0), - next_event(StateName, Record, State, Actions); + next_event(StateName, no_record, State0, Actions); #alert{} = Alert -> {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]} end. @@ -255,8 +247,7 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, try case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of {[], Buffers} -> - {Record, State} = next_record(State0#state{protocol_buffers = Buffers}), - next_event(StateName, Record, State); + next_event(StateName, no_record, State0#state{protocol_buffers = Buffers}); {Packets, Buffers} -> State = State0#state{protocol_buffers = Buffers}, Events = dtls_handshake_events(Packets), @@ -292,15 +283,12 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> renegotiate(#state{role = client} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation - {next_state, connection, State, - [{next_event, internal, #hello_request{}} | Actions]}; - + next_event(connection, no_record, State, [{next_event, internal, #hello_request{}} | Actions]); renegotiate(#state{role = server} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), State1 = prepare_flight(State0), - {State2, MoreActions} = send_handshake(HelloRequest, State1), - {Record, State} = next_record(State2), - next_event(hello, Record, State, Actions ++ MoreActions). + {State, MoreActions} = send_handshake(HelloRequest, State1), + next_event(hello, no_record, State, Actions ++ MoreActions). send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), @@ -394,23 +382,6 @@ protocol_name() -> encode_data(Data, Version, ConnectionStates0)-> dtls_record:encode_data(Data, Version, ConnectionStates0). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> - case Buffer of - <<>> -> - {Record, State} = next_record(State0), - next_event(StateName, Record, State); - _ -> - {Record, State} = ssl_connection:read_application_data(<<>>, State0), - next_event(StateName, Record, State) - end. -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; - -next_record_if_active(State) -> - next_record(State). - send(Transport, {_, {{_,_}, _} = Socket}, Data) -> send(Transport, Socket, Data); send(Transport, Socket, Data) -> @@ -452,15 +423,14 @@ init({call, From}, {start, Timeout}, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0#state{negotiated_version = Version}), {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), - State3 = State2#state{negotiated_version = Version, %% Requested version - session = - Session0#session{session_id = Hello#client_hello.session_id}, - start_or_recv_from = From, - timer = Timer, - flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} - }, - {Record, State} = next_record(State3), - next_event(hello, Record, State, Actions); + State = State2#state{negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = Hello#client_hello.session_id}, + start_or_recv_from = From, + timer = Timer, + flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} + }, + next_event(hello, no_record, State, Actions); init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> Result = gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, @@ -470,7 +440,6 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> max_ignored_alerts => 10}}), erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), Result; - init({call, _} = Type, Event, #state{role = server} = State) -> %% I.E. DTLS over sctp gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); @@ -520,9 +489,9 @@ hello(internal, #client_hello{cookie = <<>>, %% negotiated. VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), State1 = prepare_flight(State0#state{negotiated_version = Version}), - {State2, Actions} = send_handshake(VerifyRequest, State1), - {Record, State} = next_record(State2), - next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); + {State, Actions} = send_handshake(VerifyRequest, State1), + next_event(?FUNCTION_NAME, no_record, + State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, host = Host, port = Port, ssl_options = SslOpts, @@ -541,14 +510,14 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}), {State2, Actions} = send_handshake(Hello, State1), - State3 = State2#state{negotiated_version = Version, %% Requested version - session = - Session0#session{session_id = - Hello#client_hello.session_id}}, - {Record, State} = next_record(State3), - next_event(?FUNCTION_NAME, Record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, - start_or_recv_from = From} = State) -> + State = State2#state{negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = + Hello#client_hello.session_id}}, + next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, hello = Hello}, [{reply, From, {ok, Extensions}}]}; @@ -596,8 +565,7 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {Record, State2} = next_record(State1), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; hello(info, Event, State) -> @@ -648,8 +616,7 @@ certify(internal = Type, #server_hello_done{} = Event, State) -> ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {Record, State2} = next_record(State1), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; certify(state_timeout, Event, State) -> @@ -702,13 +669,11 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port, Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0), - {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), - {Record, State} = - next_record( - State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, - session = Session0#session{session_id - = Hello#client_hello.session_id}}), - next_event(hello, Record, State, Actions); + {State, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), + next_event(hello, no_record, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + session = Session0#session{session_id + = Hello#client_hello.session_id}}, + Actions); connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html @@ -928,8 +893,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName, #state{flight_state = {retransmit, NextTimeout}} = State0) -> {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}}, retransmit_epoch(StateName, State0)), - {Record, State2} = next_record(State1), - {next_state, StateName, State, Actions} = next_event(StateName, Record, State2, Actions0), + {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 55aa8174a3..3dbda2c91b 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -79,7 +79,7 @@ client_hello(Host, Port, Cookie, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites, SslOpts, ConnectionStates, - Renegotiation), + Renegotiation, undefined), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 524f06d52e..2c3f8bc20f 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -51,7 +51,7 @@ %% SSL/TLS protocol handling -export([cipher_suites/0, cipher_suites/1, cipher_suites/2, filter_cipher_suites/2, prepend_cipher_suites/2, append_cipher_suites/2, - eccs/0, eccs/1, versions/0, groups/0, + eccs/0, eccs/1, versions/0, groups/0, groups/1, format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, connection_information/1, connection_information/2]). %% Misc @@ -585,6 +585,13 @@ groups() -> tls_v1:groups(4). %%-------------------------------------------------------------------- +-spec groups(default) -> tls_v1:supported_groups(). +%% Description: returns the default groups (TLS 1.3 and later) +%%-------------------------------------------------------------------- +groups(default) -> + tls_v1:default_groups(4). + +%%-------------------------------------------------------------------- -spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> {ok, [gen_tcp:option()]} | {error, reason()}. %% @@ -988,16 +995,17 @@ handle_options(Opts0, Role, Host) -> eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()), HighestVersion), supported_groups = handle_supported_groups_option( - proplists:get_value(supported_groups, Opts, groups()), + proplists:get_value(supported_groups, Opts, groups(default)), HighestVersion), signature_algs = handle_hashsigns_option( proplists:get_value( signature_algs, Opts, - default_option_role(server, + default_option_role_sign_algs(server, tls_v1:default_signature_algs(HighestVersion), - Role)), + Role, + HighestVersion)), tls_version(HighestVersion)), signature_algs_cert = handle_signature_algorithms_option( @@ -1330,15 +1338,25 @@ validate_option(customize_hostname_check, Value) when is_list(Value) -> validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). +handle_hashsigns_option(Value, Version) when is_list(Value) + andalso Version >= {3, 4} -> + case tls_v1:signature_schemes(Version, Value) of + [] -> + throw({error, {options, + no_supported_signature_schemes, + {signature_algs, Value}}}); + _ -> + Value + end; handle_hashsigns_option(Value, Version) when is_list(Value) - andalso Version >= {3, 3} -> + andalso Version =:= {3, 3} -> case tls_v1:signature_algs(Version, Value) of [] -> throw({error, {options, no_supported_algorithms, {signature_algs, Value}}}); _ -> Value end; -handle_hashsigns_option(_, Version) when Version >= {3, 3} -> +handle_hashsigns_option(_, Version) when Version =:= {3, 3} -> handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version); handle_hashsigns_option(_, _Version) -> undefined. @@ -1755,11 +1773,20 @@ handle_verify_options(Opts, CaCerts) -> throw({error, {options, {verify, Value}}}) end. +%% Added to handle default values for signature_algs in TLS 1.3 +default_option_role_sign_algs(_, Value, _, Version) when Version >= {3,4} -> + Value; +default_option_role_sign_algs(Role, Value, Role, _) -> + Value; +default_option_role_sign_algs(_, _, _, _) -> + undefined. + default_option_role(Role, Value, Role) -> Value; default_option_role(_,_,_) -> undefined. + default_cb_info(tls) -> {gen_tcp, tcp, tcp_closed, tcp_error}; default_cb_info(dtls) -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index ff3e0d9c90..c4b8e2172a 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,7 +34,7 @@ -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, +-export([security_parameters/2, security_parameters/3, security_parameters_1_3/3, cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, aead_decrypt/6, suites/1, all_suites/1, crypto_support_filters/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, @@ -46,6 +46,9 @@ is_stream_ciphersuite/1, signature_scheme/1, scheme_to_components/1, hash_size/1]). +%% RFC 8446 TLS 1.3 +-export([generate_client_shares/1, generate_server_share/1]). + -compile(inline). -type cipher_enum() :: integer(). @@ -85,6 +88,24 @@ security_parameters(Version, CipherSuite, SecParams) -> prf_algorithm = prf_algorithm(PrfHashAlg, Version), hash_size = hash_size(Hash)}. +security_parameters_1_3(SecParams, ClientRandom, CipherSuite) -> + #{cipher := Cipher, + mac := Hash, + prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite), + SecParams#security_parameters{ + client_random = ClientRandom, + cipher_suite = CipherSuite, + bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher), + cipher_type = type(Cipher), + key_size = effective_key_bits(Cipher), + expanded_key_material_length = expanded_key_material(Cipher), + key_material_length = key_material(Cipher), + iv_size = iv_size(Cipher), + mac_algorithm = mac_algorithm(Hash), + prf_algorithm =prf_algorithm(PrfHashAlg, {3,4}), + hash_size = hash_size(Hash), + compression_algorithm = 0}. + %%-------------------------------------------------------------------- -spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}. %% @@ -1188,3 +1209,36 @@ filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) -> false -> CipherSuits -- Suites end. + +generate_server_share(Group) -> + Key = generate_key_exchange(Group), + #key_share_server_hello{ + server_share = #key_share_entry{ + group = Group, + key_exchange = Key + }}. + +generate_client_shares([]) -> + #key_share_client_hello{client_shares = []}; +generate_client_shares(Groups) -> + generate_client_shares(Groups, []). +%% +generate_client_shares([], Acc) -> + #key_share_client_hello{client_shares = lists:reverse(Acc)}; +generate_client_shares([Group|Groups], Acc) -> + Key = generate_key_exchange(Group), + KeyShareEntry = #key_share_entry{ + group = Group, + key_exchange = Key + }, + generate_client_shares(Groups, [KeyShareEntry|Acc]). + + +generate_key_exchange(secp256r1) -> + public_key:generate_key({namedCurve, secp256r1}); +generate_key_exchange(secp384r1) -> + public_key:generate_key({namedCurve, secp384r1}); +generate_key_exchange(secp521r1) -> + public_key:generate_key({namedCurve, secp521r1}); +generate_key_exchange(FFDHE) -> + public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 2abc678ed9..5d5fd6e450 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -52,8 +52,8 @@ %% Alert and close handling -export([handle_own_alert/4, handle_alert/3, - handle_normal_shutdown/3, stop/2, stop_and_reply/3 - ]). + handle_normal_shutdown/3, stop/2, stop_and_reply/3, + handle_trusted_certs_db/1]). %% Data handling -export([read_application_data/2, internal_renegotiation/2]). @@ -407,9 +407,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), - State1 = Connection:reinit_handshake_data(State0), - {Record, State} = Connection:next_record(State1#state{renegotiation = undefined}), - Connection:next_event(connection, Record, State); + State = Connection:reinit_handshake_data(State0), + Connection:next_event(connection, no_record, State#state{renegotiation = undefined}); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, #state{role = Role, @@ -419,23 +418,36 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), - {Record, State1} = Connection:next_record(State0), %% Go back to connection! - State = Connection:reinit(State1#state{renegotiation = undefined}), - Connection:next_event(connection, Record, State); + State = Connection:reinit(State0#state{renegotiation = undefined}), + Connection:next_event(connection, no_record, State); %% Gracefully log and ignore all other warning alerts handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) -> + #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State) -> log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - {Record, State} = Connection:next_record(State0), - Connection:next_event(StateName, Record, State). + Connection:next_event(StateName, no_record, State). %%==================================================================== %% Data handling %%==================================================================== + +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) -> + case Buffer of + <<>> -> + {Record, State} = Connection:next_record(State0), + Connection:next_event(StateName, Record, State); + _ -> + case read_application_data(<<>>, State0) of + {stop, _, _} = ShutdownError -> + ShutdownError; + {Record, State} -> + Connection:next_event(StateName, Record, State) + end + end. + read_application_data(Data, #state{user_application = {_Mon, Pid}, socket = Socket, protocol_cb = Connection, @@ -478,28 +490,26 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, Buffer =:= <<>> -> %% Passive mode, wait for active once or recv %% Active and empty, get more data - Connection:next_record_if_active(State); + {no_record, State}; true -> %% We have more data read_application_data(<<>>, State) end end; {more, Buffer} -> % no reply, we need more data - Connection:next_record(State0#state{user_data_buffer = Buffer}); + {no_record, State0#state{user_data_buffer = Buffer}}; {passive, Buffer} -> - Connection:next_record_if_active(State0#state{user_data_buffer = Buffer}); + {no_record, State0#state{user_data_buffer = Buffer}}; {error,_Reason} -> %% Invalid packet in packet mode deliver_packet_error(Connection:pids(State0), Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), stop(normal, State0) end. -dist_app_data(ClientData, #state{protocol_cb = Connection, - erl_dist_data = #{dist_handle := undefined, +dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined, dist_buffer := DistBuff} = DistData} = State) -> - Connection:next_record_if_active(State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}); + {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}}; dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, dist_buffer := DistBuff} = ErlDistData, - protocol_cb = Connection, user_data_buffer = Buffer, socket_options = SOpts} = State) -> Data = merge_dist_data(DistBuff, ClientData), @@ -508,7 +518,7 @@ dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, Buffer =:= <<>> -> %% Passive mode, wait for active once or recv %% Active and empty, get more data - Connection:next_record_if_active(State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}); + {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}}; _ -> %% We have more data read_application_data(<<>>, State) catch error:_ -> @@ -612,9 +622,7 @@ ssl_config(Opts, Role, State0, Type) -> init({call, From}, {start, Timeout}, State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, From), - {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From, - timer = Timer}), - Connection:next_event(hello, Record, State); + Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer}); init({call, From}, {start, {Opts, EmOpts}, Timeout}, #state{role = Role, ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0, Connection) -> @@ -727,20 +735,19 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0, + #state{role = server, expecting_next_protocol_negotiation = true} = State, Connection) -> - {Record, State} = - Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_event(?FUNCTION_NAME, Record, - State#state{expecting_next_protocol_negotiation = false}); + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}); abbreviated(internal, #change_cipher_spec{type = <<1>>}, - #state{connection_states = ConnectionStates0} = State0, Connection) -> + #state{connection_states = ConnectionStates0} = State, Connection) -> ConnectionStates1 = ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - {Record, State} = Connection:next_record(State0#state{connection_states = - ConnectionStates1}), - Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates1, + expecting_finished = true}); abbreviated(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); abbreviated(Type, Msg, State, Connection) -> @@ -769,9 +776,7 @@ certify(internal, #certificate{asn1_certificates = []}, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = State0, Connection) -> - {Record, State} = - Connection:next_record(State0#state{client_certificate_requested = false}), - Connection:next_event(?FUNCTION_NAME, Record, State); + Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); certify(internal, #certificate{}, #state{role = server, negotiated_version = Version, @@ -839,26 +844,25 @@ certify(internal, #certificate_request{}, Version, ?FUNCTION_NAME, State); certify(internal, #certificate_request{}, #state{session = #session{own_certificate = undefined}, - role = client} = State0, Connection) -> + role = client} = State, Connection) -> %% The client does not have a certificate and will send an empty reply, the server may fail %% or accept the connection by its own preference. No signature algorihms needed as there is %% no certificate to verify. - {Record, State} = Connection:next_record(State0), - Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); certify(internal, #certificate_request{} = CertRequest, #state{session = #session{own_certificate = Cert}, role = client, ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, - negotiated_version = Version} = State0, Connection) -> + negotiated_version = Version} = State, Connection) -> case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of #alert {} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - NegotiatedHashSign -> - {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), - Connection:next_event(?FUNCTION_NAME, Record, - State#state{cert_hashsign_algorithm = NegotiatedHashSign}) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); + NegotiatedHashSign -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + cert_hashsign_algorithm = NegotiatedHashSign}) end; %% PSK and RSA_PSK might bypass the Server-Key-Exchange certify(internal, #server_hello_done{}, @@ -967,7 +971,7 @@ cipher(internal, #certificate_verify{signature = Signature, negotiated_version = Version, session = #session{master_secret = MasterSecret}, tls_handshake_history = Handshake - } = State0, Connection) -> + } = State, Connection) -> TLSVersion = ssl:tls_version(Version), %% Use negotiated value if TLS-1.2 otherwhise return default @@ -975,11 +979,10 @@ cipher(internal, #certificate_verify{signature = Signature, case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, TLSVersion, HashSign, MasterSecret, Handshake) of valid -> - {Record, State} = Connection:next_record(State0), - Connection:next_event(?FUNCTION_NAME, Record, + Connection:next_event(?FUNCTION_NAME, no_record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; %% client must send a next protocol message if we are expecting it cipher(internal, #finished{}, @@ -1013,18 +1016,18 @@ cipher(internal, #finished{verify_data = Data} = Finished, %% & before finished message and it is not allowed during renegotiation cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{role = server, expecting_next_protocol_negotiation = true, - expecting_finished = true} = State0, Connection) -> - {Record, State} = - Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_event(?FUNCTION_NAME, Record, - State#state{expecting_next_protocol_negotiation = false}); + expecting_finished = true} = State, Connection) -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{expecting_next_protocol_negotiation = false, + negotiated_protocol = SelectedProtocol + }); cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = - State0, Connection) -> - ConnectionStates1 = + State, Connection) -> + ConnectionStates = ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - {Record, State} = Connection:next_record(State0#state{connection_states = - ConnectionStates1}), - Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates, + expecting_finished = true}); cipher(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1037,9 +1040,9 @@ connection({call, RecvFrom}, {recv, N, Timeout}, #state{protocol_cb = Connection, socket_options = #socket_options{active = false}} = State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - Connection:passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, - timer = Timer}, ?FUNCTION_NAME); + passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom, + timer = Timer}, ?FUNCTION_NAME, Connection); connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, Connection) -> Connection:renegotiate(State#state{renegotiation = {true, From}}, []); @@ -1081,7 +1084,7 @@ connection(cast, {dist_handshake_complete, DHandle}, connection(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); connection(internal, {recv, _}, State, Connection) -> - Connection:passive_receive(State, ?FUNCTION_NAME); + passive_receive(State, ?FUNCTION_NAME, Connection); connection(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1100,6 +1103,12 @@ downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> gen_statem:reply(From, {error, timeout}), stop(normal, State); +downgrade( + info, {CloseTag, Socket}, + #state{socket = Socket, close_tag = CloseTag, downgrade = {_, From}} = + State, _) -> + gen_statem:reply(From, {error, CloseTag}), + stop(normal, State); downgrade(Type, Event, State, Connection) -> handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection). @@ -1134,15 +1143,15 @@ handle_common_event(internal, {application_data, Data}, StateName, State0, Conne case read_application_data(Data, State0) of {stop, _, _} = Stop-> Stop; - {Record, State} -> - case Connection:next_event(StateName, Record, State) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, []); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, Actions); - {stop, _, _} = Stop -> - Stop - end + {Record, State1} -> + case Connection:next_event(StateName, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, []); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, Actions); + {stop, _, _} = Stop -> + Stop + end end; handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, #state{negotiated_version = Version} = State, _) -> @@ -1172,23 +1181,31 @@ handle_call({close, _} = Close, From, StateName, State, _Connection) -> stop_and_reply( {shutdown, normal}, {reply, From, Result}, State#state{terminated = true}); -handle_call({shutdown, How0}, From, StateName, +handle_call({shutdown, read_write = How}, From, StateName, #state{transport_cb = Transport, socket = Socket} = State, _) -> - case How0 of - How when How == write; How == both -> - send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State); - _ -> - ok - end, + try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + StateName, State) of + _ -> + case Transport:shutdown(Socket, How) of + ok -> + {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]}; + Error -> + {stop, StateName, State#state{terminated = true}, [{reply, From, Error}]} + end + catch + throw:Return -> + Return + end; +handle_call({shutdown, How0}, From, StateName, + #state{transport_cb = Transport, + socket = Socket} = State, _) -> case Transport:shutdown(Socket, How0) of ok -> - {keep_state_and_data, [{reply, From, ok}]}; + {next_state, StateName, State, [{reply, From, ok}]}; Error -> - gen_statem:reply(From, {error, Error}), - stop(normal, State) + {stop, StateName, State, [{reply, From, Error}]} end; handle_call({recv, _N, _Timeout}, From, _, #state{socket_options = @@ -1350,15 +1367,15 @@ terminate(downgrade = Reason, connection, #state{protocol_cb = Connection, handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, connection, #state{protocol_cb = Connection, - connection_states = ConnectionStates, - ssl_options = #ssl_options{padding_check = Check}, - transport_cb = Transport, socket = Socket - } = State) -> + connection_states = ConnectionStates, + ssl_options = #ssl_options{padding_check = Check}, + transport_cb = Transport, socket = Socket + } = State) -> handle_trusted_certs_db(State), Alert = terminate_alert(Reason), %% Send the termination ALERT if possible - catch (ok = Connection:send_alert_in_connection(Alert, State)), - Connection:close(Reason, Socket, Transport, ConnectionStates, Check); + catch (Connection:send_alert_in_connection(Alert, State)), + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, socket = Socket } = State) -> @@ -1514,13 +1531,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, negotiated_version = Version} = State0, Connection) -> try server_certify_and_key_exchange(State0, Connection) of #state{} = State1 -> - {State2, Actions} = server_hello_done(State1, Connection), + {State, Actions} = server_hello_done(State1, Connection), Session = Session0#session{session_id = SessionId, cipher_suite = CipherSuite, compression_method = Compression}, - {Record, State} = Connection:next_record(State2#state{session = Session}), - Connection:next_event(certify, Record, State, Actions) + Connection:next_event(certify, no_record, State#state{session = Session}, Actions) catch #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0) @@ -1535,10 +1551,9 @@ resumed_server_hello(#state{session = Session, {_, ConnectionStates1} -> State1 = State0#state{connection_states = ConnectionStates1, session = Session}, - {State2, Actions} = + {State, Actions} = finalize_handshake(State1, abbreviated, Connection), - {Record, State} = Connection:next_record(State2), - Connection:next_event(abbreviated, Record, State, Actions); + Connection:next_event(abbreviated, no_record, State, Actions); #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0) end. @@ -1560,10 +1575,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), - State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - - {Record, State} = Connection:next_record(State2), - Connection:next_event(certify, Record, State). + State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), + Connection:next_event(certify, no_record, State). handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, @@ -1621,11 +1634,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = try do_client_certify_and_key_exchange(State0, Connection) of State1 = #state{} -> {State2, Actions} = finalize_handshake(State1, certify, Connection), - State3 = State2#state{ - %% Reinitialize - client_certificate_requested = false}, - {Record, State} = Connection:next_record(State3), - Connection:next_event(cipher, Record, State, Actions) + State = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + Connection:next_event(cipher, no_record, State, Actions) catch throw:#alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0) @@ -2034,10 +2046,9 @@ calculate_master_secret(PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, + State = State0#state{connection_states = ConnectionStates, session = Session}, - {Record, State} = Connection:next_record(State1), - Connection:next_event(Next, Record, State); + Connection:next_event(Next, no_record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0) end. @@ -2110,10 +2121,9 @@ calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, calculate_secret(#server_psk_params{ hint = IdentityHint}, - State0, Connection) -> + State, Connection) -> %% store for later use - {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}), - Connection:next_event(certify, Record, State); + Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint}); calculate_secret(#server_dhe_psk_params{ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, @@ -2390,9 +2400,8 @@ prepare_connection(#state{renegotiation = Renegotiate, start_or_recv_from = RecvFrom} = State0, Connection) when Renegotiate =/= {false, first}, RecvFrom =/= undefined -> - State1 = Connection:reinit(State0), - {Record, State} = Connection:next_record(State1), - {Record, ack_connection(State)}; + State = Connection:reinit(State0), + {no_record, ack_connection(State)}; prepare_connection(State0, Connection) -> State = Connection:reinit(State0), {no_record, ack_connection(State)}. @@ -2446,26 +2455,23 @@ handle_new_session(NewId, CipherSuite, Compression, Session = Session0#session{session_id = NewId, cipher_suite = CipherSuite, compression_method = Compression}, - {Record, State} = Connection:next_record(State0#state{session = Session}), - Connection:next_event(certify, Record, State). + Connection:next_event(certify, no_record, State0#state{session = Session}). handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, negotiated_version = Version, host = Host, port = Port, protocol_cb = Connection, session_cache = Cache, - session_cache_cb = CacheCb} = State0) -> + session_cache_cb = CacheCb} = State) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {_, ConnectionStates} -> - {Record, State} = - Connection:next_record(State0#state{ - connection_states = ConnectionStates, - session = Session}), - Connection:next_event(abbreviated, Record, State); + Connection:next_event(abbreviated, no_record, State#state{ + connection_states = ConnectionStates, + session = Session}); #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State) end. make_premaster_secret({MajVer, MinVer}, rsa) -> @@ -2515,10 +2521,7 @@ handle_active_option(false, connection = StateName, To, Reply, State) -> handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection, user_data_buffer = <<>>} = State0) -> - %% Need data, set active once - {Record, State1} = Connection:next_record_if_active(State0), - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_event(StateName0, Record, State1) of + case Connection:next_event(StateName0, no_record, State0) of {next_state, StateName, State} -> hibernate_after(StateName, State, [{reply, To, Reply}]); {next_state, StateName, State, Actions} -> diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 66e3182313..91467e9b26 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -96,7 +96,8 @@ %% The mecahnism is also usefull in TLS although we do not %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. - protocol_specific = #{} :: map() + protocol_specific = #{} :: map(), + key_share }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, diff --git a/lib/ssl/src/ssl_dh_groups.erl b/lib/ssl/src/ssl_dh_groups.erl index 36c97ed13e..20d53de430 100644 --- a/lib/ssl/src/ssl_dh_groups.erl +++ b/lib/ssl/src/ssl_dh_groups.erl @@ -20,12 +20,15 @@ -module(ssl_dh_groups). +-include_lib("public_key/include/public_key.hrl"). + -export([modp2048_generator/0, modp2048_prime/0, ffdhe2048_generator/0, ffdhe2048_prime/0, ffdhe3072_generator/0, ffdhe3072_prime/0, ffdhe4096_generator/0, ffdhe4096_prime/0, ffdhe6144_generator/0, ffdhe6144_prime/0, - ffdhe8192_generator/0, ffdhe8192_prime/0]). + ffdhe8192_generator/0, ffdhe8192_prime/0, + dh_params/1]). %% RFC3526 - 2048-bit MODP Group %% This group is assigned id 14. @@ -441,3 +444,24 @@ ffdhe8192_prime() -> "97D11D49" "F7A8443D" "0822E506" "A9F4614E" "011E2A94" "838FF88C" "D68C8BB7" "C5C6424C" "FFFFFFFF" "FFFFFFFF", list_to_integer(P, 16). + +dh_params(ffdhe2048) -> + #'DHParameter'{ + prime = ffdhe2048_prime(), + base = ffdhe2048_generator()}; +dh_params(ffdhe3072) -> + #'DHParameter'{ + prime = ffdhe3072_prime(), + base = ffdhe3072_generator()}; +dh_params(ffdhe4096) -> + #'DHParameter'{ + prime = ffdhe4096_prime(), + base = ffdhe4096_generator()}; +dh_params(ffdhe6144) -> + #'DHParameter'{ + prime = ffdhe6144_prime(), + base = ffdhe6144_generator()}; +dh_params(ffdhe8192) -> + #'DHParameter'{ + prime = ffdhe8192_prime(), + base = ffdhe8192_generator()}. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 39a627c0a2..417e5d9eb6 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -30,6 +30,7 @@ -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). +-include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). -export_type([ssl_handshake/0, ssl_handshake_history/0, @@ -60,7 +61,7 @@ -export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode --export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/2, +-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/3, decode_server_key/3, decode_client_key/3, decode_suites/2 ]). @@ -71,13 +72,15 @@ premaster_secret/2, premaster_secret/3, premaster_secret/4]). %% Extensions handling --export([client_hello_extensions/5, +-export([client_hello_extensions/6, handle_client_hello_extensions/9, %% Returns server hello extensions handle_server_hello_extensions/9, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, - select_hashsign_algs/3, empty_extensions/2 + select_hashsign_algs/3, empty_extensions/2, add_server_share/2 ]). +-export([get_cert_params/1]). + %%==================================================================== %% Create handshake messages %%==================================================================== @@ -679,12 +682,29 @@ encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> VerLen = byte_size(Versions), Len = VerLen + 2, encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), - ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); + ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) -> Version = encode_versions([Version0]), Len = byte_size(Version), %% 2 encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), - ?UINT16(Len), Version/binary, Acc/binary>>). + ?UINT16(Len), Version/binary, Acc/binary>>); +encode_extensions([#key_share_client_hello{client_shares = ClientShares0} | Rest], Acc) -> + ClientShares = encode_client_shares(ClientShares0), + ClientSharesLen = byte_size(ClientShares), + Len = ClientSharesLen + 2, + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(Len), ?UINT16(ClientSharesLen), + ClientShares/binary, Acc/binary>>); +encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], Acc) -> + ServerShare = encode_key_share_entry(ServerShare0), + Len = byte_size(ServerShare), + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(Len), ServerShare/binary, Acc/binary>>); +encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> + Group = tls_v1:group_to_enum(Group0), + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(2), ?UINT16(Group), Acc/binary>>). + encode_client_protocol_negotiation(undefined, _) -> undefined; @@ -726,7 +746,7 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32 Cipher_suite:2/binary, ?BYTE(Comp_method), ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> - HelloExtensions = decode_hello_extensions(Extensions, Version, server), + HelloExtensions = decode_hello_extensions(Extensions, Version, server_hello), #server_hello{ server_version = {Major,Minor}, @@ -783,25 +803,27 @@ decode_vector(<<?UINT16(Len), Vector:Len/binary>>) -> Vector. %%-------------------------------------------------------------------- --spec decode_hello_extensions(binary(), ssl_record:ssl_version(), client | server) -> map(). +-spec decode_hello_extensions(binary(), ssl_record:ssl_version(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- -decode_hello_extensions(Extensions, Version, Role) -> +decode_hello_extensions(Extensions, Version, MessageType0) -> + %% Convert legacy atoms MessageType = - case Role of + case MessageType0 of client -> client_hello; - server -> server_hello + server -> server_hello; + T -> T end, - decode_extensions(Extensions, Version, empty_extensions(Version, MessageType)). + decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)). %%-------------------------------------------------------------------- --spec decode_extensions(binary(),tuple()) -> map(). +-spec decode_extensions(binary(),tuple(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- -decode_extensions(Extensions, Version) -> - decode_extensions(Extensions, Version, empty_extensions()). +decode_extensions(Extensions, Version, MessageType) -> + decode_extensions(Extensions, Version, MessageType, empty_extensions()). %%-------------------------------------------------------------------- -spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> @@ -1011,21 +1033,20 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> %%==================================================================== %% Extensions handling %%==================================================================== -client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> +client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare) -> HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation), HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts), - maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts). + maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare). -add_tls12_extensions(Version, - #ssl_options{signature_algs = SupportedHashSigns} = SslOpts, +add_tls12_extensions(_Version, + SslOpts, ConnectionStates, Renegotiation) -> SRP = srp_user(SslOpts), #{renegotiation_info => renegotiation_info(tls_record, client, ConnectionStates, Renegotiation), srp => SRP, - signature_algs => available_signature_algs(SupportedHashSigns, Version), alpn => encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), next_protocol_negotiation => encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, @@ -1038,16 +1059,19 @@ add_common_extensions({3,4}, HelloExtensions, _CipherSuites, #ssl_options{eccs = SupportedECCs, - supported_groups = Groups}) -> + supported_groups = Groups, + signature_algs = SignatureSchemes}) -> {EcPointFormats, _} = client_ecc_extensions(SupportedECCs), HelloExtensions#{ec_point_formats => EcPointFormats, - elliptic_curves => Groups}; + elliptic_curves => Groups, + signature_algs => signature_algs_ext(SignatureSchemes)}; -add_common_extensions(_Version, +add_common_extensions(Version, HelloExtensions, CipherSuites, - #ssl_options{eccs = SupportedECCs}) -> + #ssl_options{eccs = SupportedECCs, + signature_algs = SupportedHashSigns}) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers( @@ -1059,20 +1083,93 @@ add_common_extensions(_Version, {undefined, undefined} end, HelloExtensions#{ec_point_formats => EcPointFormats, - elliptic_curves => EllipticCurves}. + elliptic_curves => EllipticCurves, + signature_algs => available_signature_algs(SupportedHashSigns, Version)}. maybe_add_tls13_extensions({3,4}, - HelloExtensions, + HelloExtensions0, #ssl_options{signature_algs_cert = SignatureSchemes, - versions = SupportedVersions}) -> - HelloExtensions#{client_hello_versions => - #client_hello_versions{versions = SupportedVersions}, - signature_algs_cert => - signature_algs_cert(SignatureSchemes)}; -maybe_add_tls13_extensions(_, HelloExtensions, _) -> + versions = SupportedVersions}, + KeyShare) -> + HelloExtensions = + HelloExtensions0#{client_hello_versions => + #client_hello_versions{versions = SupportedVersions}, + signature_algs_cert => + signature_algs_cert(SignatureSchemes)}, + maybe_add_key_share(HelloExtensions, KeyShare); +maybe_add_tls13_extensions(_, HelloExtensions, _, _) -> HelloExtensions. + +%% TODO: Add support for PSK key establishment + +%% RFC 8446 (TLS 1.3) - 4.2.8. Key Share +%% +%% 4.2.8.1. Diffie-Hellman Parameters +%% Diffie-Hellman [DH76] parameters for both clients and servers are +%% encoded in the opaque key_exchange field of a KeyShareEntry in a +%% KeyShare structure. The opaque value contains the Diffie-Hellman +%% public value (Y = g^X mod p) for the specified group (see [RFC7919] +%% for group definitions) encoded as a big-endian integer and padded to +%% the left with zeros to the size of p in bytes. +%% +%% 4.2.8.2. ECDHE Parameters +%% +%% ECDHE parameters for both clients and servers are encoded in the +%% opaque key_exchange field of a KeyShareEntry in a KeyShare structure. +%% +%% For secp256r1, secp384r1, and secp521r1, the contents are the +%% serialized value of the following struct: +%% +%% struct { +%% uint8 legacy_form = 4; +%% opaque X[coordinate_length]; +%% opaque Y[coordinate_length]; +%% } UncompressedPointRepresentation; +%% +%% X and Y, respectively, are the binary representations of the x and y +%% values in network byte order. There are no internal length markers, +%% so each number representation occupies as many octets as implied by +%% the curve parameters. For P-256, this means that each of X and Y use +%% 32 octets, padded on the left by zeros if necessary. For P-384, they +%% take 48 octets each. For P-521, they take 66 octets each. +maybe_add_key_share(HelloExtensions, undefined) -> + HelloExtensions; +maybe_add_key_share(HelloExtensions, KeyShare) -> + #key_share_client_hello{client_shares = ClientShares0} = KeyShare, + %% Keep only public keys + ClientShares = lists:map(fun kse_remove_private_key/1, ClientShares0), + HelloExtensions#{key_share => #key_share_client_hello{ + client_shares = ClientShares}}. + +add_server_share(Extensions, KeyShare) -> + #key_share_server_hello{server_share = ServerShare0} = KeyShare, + %% Keep only public keys + ServerShare = kse_remove_private_key(ServerShare0), + Extensions#{key_share => #key_share_server_hello{ + server_share = ServerShare}}. + +kse_remove_private_key(#key_share_entry{ + group = Group, + key_exchange = + #'ECPrivateKey'{publicKey = PublicKey}}) -> + #key_share_entry{ + group = Group, + key_exchange = PublicKey}; +kse_remove_private_key(#key_share_entry{ + group = Group, + key_exchange = + {PublicKey, _}}) -> + #key_share_entry{ + group = Group, + key_exchange = PublicKey}. + +signature_algs_ext(undefined) -> + undefined; +signature_algs_ext(SignatureSchemes) -> + #signature_algorithms{signature_scheme_list = SignatureSchemes}. + signature_algs_cert(undefined) -> undefined; signature_algs_cert(SignatureSchemes) -> @@ -1948,6 +2045,20 @@ encode_versions([], Acc) -> encode_versions([{M,N}|T], Acc) -> encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>). +encode_client_shares(ClientShares) -> + encode_client_shares(ClientShares, <<>>). +%% +encode_client_shares([], Acc) -> + Acc; +encode_client_shares([KeyShareEntry0|T], Acc) -> + KeyShareEntry = encode_key_share_entry(KeyShareEntry0), + encode_client_shares(T, <<Acc/binary,KeyShareEntry/binary>>). + +encode_key_share_entry(#key_share_entry{ + group = Group, + key_exchange = KeyExchange}) -> + Len = byte_size(KeyExchange), + <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>. hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2090,19 +2201,19 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> dec_server_key_signature(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). -decode_extensions(<<>>, _Version, Acc) -> +decode_extensions(<<>>, _Version, _MessageType, Acc) -> Acc; decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), - ExtensionData:Len/binary, Rest/binary>>, Version, Acc) + ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len + 2 =:= ExtLen -> ALPN = #alpn{extension_data = ExtensionData}, - decode_extensions(Rest, Version, Acc#{alpn => ALPN}); + decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN}); decode_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), - ExtensionData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> NextP = #next_protocol_negotiation{extension_data = ExtensionData}, - decode_extensions(Rest, Version, Acc#{next_protocol_negotiation => NextP}); + decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP}); decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), - Info:Len/binary, Rest/binary>>, Version, Acc) -> + Info:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> RenegotiateInfo = case Len of 1 -> % Initial handshake Info; % should be <<0>> will be matched in handle_renegotiation_info @@ -2111,49 +2222,53 @@ decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info, VerifyInfo end, - decode_extensions(Rest, Version, Acc#{renegotiation_info => - #renegotiation_info{renegotiated_connection = - RenegotiateInfo}}); + decode_extensions(Rest, Version, MessageType, + Acc#{renegotiation_info => + #renegotiation_info{renegotiated_connection = + RenegotiateInfo}}); decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), - SRP:SRPLen/binary, Rest/binary>>, Version, Acc) + SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc) when Len == SRPLen + 2 -> - decode_extensions(Rest, Version, Acc#{srp => #srp{username = SRP}}); + decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version < {3,4} -> SignAlgoListLen = Len - 2, <<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData, HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || <<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList], - decode_extensions(Rest, Version, Acc#{signature_algs => - #hash_sign_algos{hash_sign_algos = - HashSignAlgos}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs => + #hash_sign_algos{hash_sign_algos = + HashSignAlgos}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version =:= {3,4} -> SignSchemeListLen = Len - 2, <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData, SignSchemes = [ssl_cipher:signature_scheme(SignScheme) || <<?UINT16(SignScheme)>> <= SignSchemeList], - decode_extensions(Rest, Version, Acc#{signature_algs => - #signature_algorithms{ - signature_scheme_list = SignSchemes}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs => + #signature_algorithms{ + signature_scheme_list = SignSchemes}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData, SignSchemes = [ssl_cipher:signature_scheme(SignScheme) || <<?UINT16(SignScheme)>> <= SignSchemeList], - decode_extensions(Rest, Version, Acc#{signature_algs_cert => - #signature_algorithms_cert{ - signature_scheme_list = SignSchemes}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs_cert => + #signature_algorithms_cert{ + signature_scheme_list = SignSchemes}}); decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version < {3,4} -> <<?UINT16(_), EllipticCurveList/binary>> = ExtData, %% Ignore unknown curves @@ -2166,13 +2281,13 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), end end, EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]), - decode_extensions(Rest, Version, Acc#{elliptic_curves => - #elliptic_curves{elliptic_curve_list = - EllipticCurves}}); - + decode_extensions(Rest, Version, MessageType, + Acc#{elliptic_curves => + #elliptic_curves{elliptic_curve_list = + EllipticCurves}}); decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version =:= {3,4} -> <<?UINT16(_), GroupList/binary>> = ExtData, %% Ignore unknown curves @@ -2185,47 +2300,84 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), end end, SupportedGroups = lists:filtermap(Pick, [Group || <<Group:16>> <= GroupList]), - decode_extensions(Rest, Version, Acc#{elliptic_curves => - #supported_groups{supported_groups = - SupportedGroups}}); + decode_extensions(Rest, Version, MessageType, + Acc#{elliptic_curves => + #supported_groups{supported_groups = + SupportedGroups}}); decode_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> <<?BYTE(_), ECPointFormatList/binary>> = ExtData, ECPointFormats = binary_to_list(ECPointFormatList), - decode_extensions(Rest, Version, Acc#{ec_point_formats => - #ec_point_formats{ec_point_format_list = - ECPointFormats}}); + decode_extensions(Rest, Version, MessageType, + Acc#{ec_point_formats => + #ec_point_formats{ec_point_format_list = + ECPointFormats}}); decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), - Rest/binary>>, Version, Acc) when Len == 0 -> - decode_extensions(Rest, Version, Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI + Rest/binary>>, Version, MessageType, Acc) when Len == 0 -> + decode_extensions(Rest, Version, MessageType, + Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> <<?UINT16(_), NameList/binary>> = ExtData, - decode_extensions(Rest, Version, Acc#{sni => dec_sni(NameList)}); + decode_extensions(Rest, Version, MessageType, + Acc#{sni => dec_sni(NameList)}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) when Len > 2 -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 -> <<?UINT16(_),Versions/binary>> = ExtData, - decode_extensions(Rest, Version, Acc#{client_hello_versions => - #client_hello_versions{ - versions = decode_versions(Versions)}}); + decode_extensions(Rest, Version, MessageType, + Acc#{client_hello_versions => + #client_hello_versions{ + versions = decode_versions(Versions)}}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), - ?UINT16(SelectedVersion), Rest/binary>>, Version, Acc) + ?UINT16(SelectedVersion), Rest/binary>>, Version, MessageType, Acc) when Len =:= 2, SelectedVersion =:= 16#0304 -> - decode_extensions(Rest, Version, Acc#{server_hello_selected_version => - #server_hello_selected_version{selected_version = - {3,4}}}); + decode_extensions(Rest, Version, MessageType, + Acc#{server_hello_selected_version => + #server_hello_selected_version{selected_version = + {3,4}}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = client_hello, Acc) -> + <<?UINT16(_),ClientShares/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_client_hello{ + client_shares = decode_client_shares(ClientShares)}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = server_hello, Acc) -> + <<?UINT16(Group),?UINT16(KeyLen),KeyExchange:KeyLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_server_hello{ + server_share = + #key_share_entry{ + group = tls_v1:enum_to_group(Group), + key_exchange = KeyExchange}}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = hello_retry_request, Acc) -> + <<?UINT16(Group),Rest/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_hello_retry_request{ + selected_group = tls_v1:enum_to_group(Group)}}); + %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. -decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, Acc) -> - decode_extensions(Rest, Version, Acc); +decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> + decode_extensions(Rest, Version, MessageType, Acc); %% This theoretically should not happen if the protocol is followed, but if it does it is ignored. -decode_extensions(_, _, Acc) -> +decode_extensions(_, _, _, Acc) -> Acc. dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) -> @@ -2252,6 +2404,17 @@ decode_versions(<<?BYTE(M),?BYTE(N),Rest/binary>>, Acc) -> decode_versions(Rest, [{M,N}|Acc]). +decode_client_shares(ClientShares) -> + decode_client_shares(ClientShares, []). +%% +decode_client_shares(<<>>, Acc) -> + lists:reverse(Acc); +decode_client_shares(<<?UINT16(Group),?UINT16(Len),KeyExchange:Len/binary,Rest/binary>>, Acc) -> + decode_client_shares(Rest, [#key_share_entry{ + group = tls_v1:enum_to_group(Group), + key_exchange= KeyExchange + }|Acc]). + decode_next_protocols({next_protocol_negotiation, Protocols}) -> decode_protocols(Protocols, []). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 1fd143a641..d4233bea9b 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -107,7 +107,8 @@ sni, client_hello_versions, server_hello_selected_version, - signature_algs_cert + signature_algs_cert, + key_share }). -record(server_hello, { diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 48798799f7..a079c6a796 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -61,6 +61,7 @@ -define(CDR_MAGIC, "GIOP"). -define(CDR_HDR_SIZE, 12). +-define(INTERNAL_ACTIVE_N, 100). -define(DEFAULT_TIMEOUT, 5000). -define(NO_DIST_POINT, "http://dummy/no_distribution_point"). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 29988edf76..ae784380c8 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -47,7 +47,7 @@ -export([start_fsm/8, start_link/8, init/1, pids/1]). %% State transition handling --export([next_record/1, next_event/3, next_event/4, +-export([next_event/3, next_event/4, handle_common_event/4]). %% Handshake handling @@ -62,7 +62,7 @@ encode_alert/3, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, +-export([encode_data/3, next_record/1, send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions @@ -88,6 +88,7 @@ %% gen_statem callbacks -export([callback_mode/0, terminate/3, code_change/4, format_status/2]). +-export([encode_handshake/4]). -define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). @@ -180,30 +181,30 @@ next_record(#state{protocol_buffers = {Alert, State} end; next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, - socket = Socket, + protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + socket = Socket, close_tag = CloseTag, transport_cb = Transport} = State) -> - case tls_socket:setopts(Transport, Socket, [{active,once}]) of - ok -> - {no_record, State}; - _ -> - self() ! {CloseTag, Socket}, - {no_record, State} - end; + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end; next_record(State) -> {no_record, State}. next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). - -next_event(connection = StateName, no_record, State0, Actions) -> - case next_record_if_active(State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {#ssl_tls{} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#alert{} = Alert, State} -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} +next_event(StateName, no_record, State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + {next_state, StateName, State, Actions}; + {#ssl_tls{} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#alert{} = Alert, State} -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; next_event(StateName, Record, State, Actions) -> case Record of @@ -225,23 +226,23 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, negotiated_version = Version, ssl_options = Options} = State0) -> try - {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options), - State1 = + EffectiveVersion = effective_version(Version, Options), + {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), + State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, case Packets of [] -> assert_buffer_sanity(Buf, Options), - {Record, State} = next_record(State1), - next_event(StateName, Record, State); + next_event(StateName, no_record, State); _ -> Events = tls_handshake_events(Packets), case StateName of connection -> - ssl_connection:hibernate_after(StateName, State1, Events); + ssl_connection:hibernate_after(StateName, State, Events); _ -> {next_state, StateName, - State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} end end catch throw:#alert{} = Alert -> @@ -296,11 +297,10 @@ renegotiate(#state{role = server, {BinMsg, ConnectionStates} = tls_record:encode_handshake(Frag, Version, ConnectionStates0), send(Transport, Socket, BinMsg), - State1 = State0#state{connection_states = + State = State0#state{connection_states = ConnectionStates, tls_handshake_history = Hs0}, - {Record, State} = next_record(State1), - next_event(hello, Record, State, Actions). + next_event(hello, no_record, State, Actions). send_handshake(Handshake, State) -> send_handshake_flight(queue_handshake(Handshake, State)). @@ -406,13 +406,11 @@ send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> send_alert_in_connection(Alert, #state{protocol_specific = #{sender := Sender}}) -> tls_sender:send_alert(Sender, Alert). -send_sync_alert(Alert, #state{protocol_specific = #{sender := Sender}}= State) -> - tls_sender:send_and_ack_alert(Sender, Alert), - receive - {Sender, ack_alert} -> - ok - after ?DEFAULT_TIMEOUT -> - %% Sender is blocked terminate anyway +send_sync_alert( + Alert, #state{protocol_specific = #{sender := Sender}} = State) -> + try tls_sender:send_and_ack_alert(Sender, Alert) + catch + _:_ -> throw({stop, {shutdown, own_alert}, State}) end. @@ -450,23 +448,6 @@ protocol_name() -> encode_data(Data, Version, ConnectionStates0)-> tls_record:encode_data(Data, Version, ConnectionStates0). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> - case Buffer of - <<>> -> - {Record, State} = next_record(State0), - next_event(StateName, Record, State); - _ -> - {Record, State} = ssl_connection:read_application_data(<<>>, State0), - next_event(StateName, Record, State) - end. - -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; -next_record_if_active(State) -> - next_record(State). - send(Transport, Socket, Data) -> tls_socket:send(Transport, Socket, Data). @@ -498,12 +479,12 @@ init({call, From}, {start, Timeout}, session_cache = Cache, session_cache_cb = CacheCb } = State0) -> + KeyShare = maybe_generate_client_shares(SslOpts), Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Cache, CacheCb, Renegotiation, Cert), - - Version = Hello#client_hello.client_version, - HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions), + Cache, CacheCb, Renegotiation, Cert, KeyShare), + + HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions), Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), @@ -516,15 +497,16 @@ init({call, From}, {start, Timeout}, message => Hello}, ssl_logger:debug(SslOpts#ssl_options.log_level, HelloMsg, #{domain => [otp,ssl,handshake]}), ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - State1 = State0#state{connection_states = ConnectionStates, - negotiated_version = Version, %% Requested version - session = - Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake, - start_or_recv_from = From, - timer = Timer}, - {Record, State} = next_record(State1), - next_event(hello, Record, State); + State = State0#state{connection_states = ConnectionStates, + negotiated_version = HelloVersion, %% Requested version + session = + Session0#session{session_id = Hello#client_hello.session_id}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer, + key_share = KeyShare}, + next_event(hello, no_record, State); + init(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). @@ -569,41 +551,36 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, negotiated_protocol = CurrentProtocol, key_algorithm = KeyExAlg, ssl_options = SslOpts} = State) -> - case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, ClientVersion, hello, - State#state{negotiated_version - = ClientVersion}); - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} when Version < {3,4} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - gen_handshake(?FUNCTION_NAME, internal, {common_client_hello, Type, ServerHelloExt}, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - session = Session, - negotiated_protocol = Protocol}); - %% TLS 1.3 - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - tls_connection_1_3:gen_handshake(?FUNCTION_NAME, - internal, - {common_client_hello, Type, ServerHelloExt}, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - session = Session, - negotiated_protocol = Protocol}) + case choose_tls_version(SslOpts, Hello) of + 'tls_v1.3' -> + %% Continue in TLS 1.3 'start' state + {next_state, start, State, [{next_event, internal, Hello}]}; + 'tls_v1.2' -> + case tls_handshake:hello(Hello, + SslOpts, + {Port, Session0, Cache, CacheCb, + ConnectionStates0, Cert, KeyExAlg}, + Renegotiation) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, ClientVersion, hello, + State#state{negotiated_version + = ClientVersion}); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + gen_handshake(?FUNCTION_NAME, + internal, + {common_client_hello, Type, ServerHelloExt}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + hashsign_algorithm = HashSign, + client_hello_version = ClientVersion, + session = Session, + negotiated_protocol = Protocol}) + end end; hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, @@ -674,37 +651,34 @@ connection(internal, #hello_request{}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - Cache, CacheCb, Renegotiation, Cert), - {State1, Actions} = send_handshake(Hello, State0), - {Record, State} = - next_record( - State1#state{session = Session0#session{session_id - = Hello#client_hello.session_id}}), - next_event(hello, Record, State, Actions); + Cache, CacheCb, Renegotiation, Cert, undefined), + {State, Actions} = send_handshake(Hello, State0), + next_event(hello, no_record, State#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}, Actions); connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true, connection_states = CS, %%protocol_cb = Connection, protocol_specific = #{sender := Sender} - } = State0) -> + } = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {Record, State} = next_record(State0#state{allow_renegotiate = false, - renegotiation = {true, peer}}), {ok, Write} = tls_sender:renegotiate(Sender), - next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}}, + next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write}, + allow_renegotiate = false, + renegotiation = {true, peer} + }, [{next_event, internal, Hello}]); connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false, protocol_cb = Connection} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), send_alert_in_connection(Alert, State0), - State1 = Connection:reinit_handshake_data(State0), - {Record, State} = next_record(State1), - next_event(?FUNCTION_NAME, Record, State); + State = Connection:reinit_handshake_data(State0), + next_event(?FUNCTION_NAME, no_record, State); connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -723,108 +697,108 @@ downgrade(Type, Event, State) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- start(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); start(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec negotiated(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- negotiated(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); negotiated(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec recvd_ch(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- recvd_ch(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); recvd_ch(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_cert(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_cert(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_cert(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_cv(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_cv(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_cv(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_eoed(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_eoed(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_eoed(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_finished(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_finished(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_finished(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_flight2(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_flight2(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_flight2(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connected(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connected(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); connected(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_cert_cr(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_cert_cr(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_ee(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_ee(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_ee(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_sh(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- wait_sh(info, Event, State) -> - gen_info(Event, ?FUNCTION_NAME, State); + gen_info_1_3(Event, ?FUNCTION_NAME, State); wait_sh(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). %-------------------------------------------------------------------- %% gen_statem callbacks @@ -832,6 +806,12 @@ wait_sh(Type, Event, State) -> callback_mode() -> state_functions. + +terminate( + {shutdown, sender_died, Reason}, _StateName, + #state{socket = Socket, transport_cb = Transport} = State) -> + ssl_connection:handle_trusted_certs_db(State), + close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> catch ssl_connection:terminate(Reason, StateName, State), ensure_sender_terminate(Reason, State). @@ -850,7 +830,6 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac #ssl_options{beast_mitigation = BeastMitigation, erl_dist = IsErlDist} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), - ErlDistData = erl_dist_data(IsErlDist), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -858,6 +837,13 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac _ -> ssl_session_cache end, + + InternalActiveN = case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) andalso (not IsErlDist) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end, UserMonitor = erlang:monitor(process, User), @@ -884,7 +870,10 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac protocol_cb = ?MODULE, tracker = Tracker, flight_buffer = [], - protocol_specific = #{sender => Sender} + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } }. erl_dist_data(true) -> @@ -955,7 +944,8 @@ tls_handshake_events(Packets) -> %% raw data from socket, upack records handle_info({Protocol, _, Data}, StateName, - #state{data_tag = Protocol} = State0) -> + #state{data_tag = Protocol + } = State0) -> case next_tls_record(Data, StateName, State0) of {Record, State} -> next_event(StateName, Record, State); @@ -963,11 +953,16 @@ handle_info({Protocol, _, Data}, StateName, ssl_connection:handle_normal_shutdown(Alert, StateName, State0), ssl_connection:stop({shutdown, own_alert}, State0) end; +handle_info({tcp_passive, Socket}, StateName, #state{socket = Socket, + protocol_specific = PS + } = State) -> + next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}); handle_info({CloseTag, Socket}, StateName, #state{socket = Socket, close_tag = CloseTag, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, user_data_buffer = Buffer, + protocol_specific = PS, negotiated_version = Version} = State) -> %% Note that as of TLS 1.1, @@ -993,8 +988,9 @@ handle_info({CloseTag, Socket}, StateName, true -> %% Fixes non-delivery of final TLS record in {active, once}. %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. - next_event(StateName, no_record, State) + %% and then receive the final message. Set internal active_n to zero + %% to ensure socket close message is sent if there is not enough data to deliver. + next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}) end; handle_info({'EXIT', Sender, Reason}, _, #state{protocol_specific = #{sender := Sender}} = State) -> @@ -1036,6 +1032,18 @@ gen_handshake(StateName, Type, Event, Version, StateName, State) end. +gen_handshake_1_3(StateName, Type, Event, + #state{negotiated_version = Version} = State) -> + try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> try handle_info(Event, StateName, State) of Result -> @@ -1057,6 +1065,29 @@ gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> malformed_handshake_data), Version, StateName, State) end. + +gen_info_1_3(Event, connected = StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + malformed_data), + Version, StateName, State) + end; + +gen_info_1_3(Event, StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + unprocessed_events(Events) -> %% The first handshake event will be processed immediately @@ -1101,3 +1132,34 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> end end, spawn(Kill). + +maybe_generate_client_shares(#ssl_options{ + versions = [Version|_], + supported_groups = + #supported_groups{ + supported_groups = Groups}}) + when Version =:= {3,4} -> + ssl_cipher:generate_client_shares(Groups); +maybe_generate_client_shares(_) -> + undefined. + +choose_tls_version(#ssl_options{versions = Versions}, + #client_hello{ + extensions = #{client_hello_versions := + #client_hello_versions{versions = ClientVersions} + } + }) -> + case ssl_handshake:select_supported_version(ClientVersions, Versions) of + {3,4} -> + 'tls_v1.3'; + _Else -> + 'tls_v1.2' + end; +choose_tls_version(_, _) -> + 'tls_v1.2'. + + +effective_version(undefined, #ssl_options{versions = [Version|_]}) -> + Version; +effective_version(Version, _) -> + Version. diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index c8732e7847..04bcea1e1b 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -104,56 +104,97 @@ -include("ssl_alert.hrl"). -include("ssl_connection.hrl"). +-include("tls_handshake.hrl"). +-include("tls_handshake_1_3.hrl"). --export([hello/4]). --export([gen_handshake/4]). +%% gen_statem helper functions +-export([start/4, + negotiated/4 + ]). -hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> - do_server_hello(Type, ServerHelloExt, State, Connection). +start(internal, + #client_hello{} = Hello, + #state{connection_states = _ConnectionStates0, + ssl_options = #ssl_options{ciphers = _ServerCiphers, + signature_algs = _ServerSignAlgs, + signature_algs_cert = _SignatureSchemes, %% TODO: Check?? + supported_groups = _ServerGroups0, + versions = _Versions} = SslOpts, + session = #session{own_certificate = Cert}} = State0, + _Module) -> + + Env = #{cert => Cert}, + case tls_handshake_1_3:handle_client_hello(Hello, SslOpts, Env) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + M -> + %% update connection_states with cipher + State = update_state(State0, M), + {next_state, negotiated, State, [{next_event, internal, M}]} + + end. + +%% TODO: move these functions +update_state(#state{connection_states = ConnectionStates0, + session = Session} = State, + #{client_random := ClientRandom, + cipher := Cipher, + key_share := KeyShare, + session_id := SessionId}) -> + #{security_parameters := SecParamsR0} = PendingRead = + maps:get(pending_read, ConnectionStates0), + #{security_parameters := SecParamsW0} = PendingWrite = + maps:get(pending_write, ConnectionStates0), + SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, ClientRandom, Cipher), + SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, ClientRandom, Cipher), + ConnectionStates = + ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, + pending_write => PendingWrite#{security_parameters => SecParamsW}}, + State#state{connection_states = ConnectionStates, + key_share = KeyShare, + session = Session#session{session_id = SessionId}}. + + +negotiated(internal, + Map, + #state{connection_states = ConnectionStates0, + session = #session{session_id = SessionId}, + ssl_options = #ssl_options{} = SslOpts, + key_share = KeyShare, + tls_handshake_history = HHistory0, + transport_cb = Transport, + socket = Socket}, _Module) -> + + %% Create server_hello + %% Extensions: supported_versions, key_share, (pre_shared_key) + ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare, + ConnectionStates0, Map), + + %% Update handshake_history (done in encode!) + %% Encode handshake + {BinMsg, _ConnectionStates, _HHistory} = + tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0), + %% Send server_hello + tls_connection:send(Transport, Socket, BinMsg), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinMsg}, + Msg = #{direction => outbound, + protocol => 'handshake', + message => ServerHello}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}), + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), + ok. + + %% K_send = handshake ??? + %% (Send EncryptedExtensions) + %% ([Send CertificateRequest]) + %% [Send Certificate + CertificateVerify] + %% Send Finished + %% K_send = application ??? -do_server_hello(Type, #{next_protocol_negotiation := _NextProtocols} = - _ServerHelloExt, - #state{negotiated_version = _Version, - session = #session{session_id = _SessId}, - connection_states = _ConnectionStates0, - ssl_options = #ssl_options{versions = [_HighestVersion|_]}} - = State0, _Connection) when is_atom(Type) -> -%% NEGOTIATED -%% | Send ServerHello -%% | K_send = handshake -%% | Send EncryptedExtensions -%% | [Send CertificateRequest] -%% Can send | [Send Certificate + CertificateVerify] -%% app data | Send Finished -%% after --> | K_send = application -%% here +--------+--------+ -%% No 0-RTT | | 0-RTT -%% | | -%% K_recv = handshake | | K_recv = early data -%% [Skip decrypt errors] | +------> WAIT_EOED -+ -%% | | Recv | | Recv EndOfEarlyData -%% | | early data | | K_recv = handshake -%% | +------------+ | -%% | | -%% +> WAIT_FLIGHT2 <--------+ %% Will be called implicitly %% {Record, State} = Connection:next_record(State2#state{session = Session}), %% Connection:next_event(wait_flight2, Record, State, Actions), %% OR %% Connection:next_event(WAIT_EOED, Record, State, Actions) - {next_state, wait_flight2, State0, []}. - %% TODO: Add new states to tls_connection! - %% State0. - - -gen_handshake(StateName, Type, Event, - #state{negotiated_version = Version} = State) -> - try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) - end. diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 37f13fcbac..5aca4bf8c8 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -26,6 +26,7 @@ -module(tls_handshake). -include("tls_handshake.hrl"). +-include("tls_handshake_1_3.hrl"). -include("tls_record.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). @@ -34,7 +35,7 @@ -include_lib("kernel/include/logger.hrl"). %% Handshake handling --export([client_hello/8, hello/4]). +-export([client_hello/9, hello/4]). %% Handshake encoding -export([encode_handshake/2]). @@ -49,7 +50,8 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), - #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #ssl_options{}, integer(), atom(), boolean(), der_cert(), + #key_share_client_hello{} | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -59,7 +61,7 @@ client_hello(Host, Port, ConnectionStates, ciphers = UserSuites, fallback = Fallback } = SslOpts, - Cache, CacheCb, Renegotiation, OwnCert) -> + Cache, CacheCb, Renegotiation, OwnCert, KeyShare) -> Version = tls_record:highest_protocol_version(Versions), %% In TLS 1.3, the client indicates its version preferences in the @@ -79,7 +81,8 @@ client_hello(Host, Port, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(Version, AvailableCipherSuites, SslOpts, ConnectionStates, - Renegotiation), + Renegotiation, + KeyShare), CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, @@ -260,8 +263,6 @@ get_tls_handshake(Version, Data, Buffer, Options) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_client_hello(Version = {3,4}, ClientHello, SslOpts, Info, Renegotiation) -> - tls_handshake_1_3:handle_client_hello(Version, ClientHello, SslOpts, Info, Renegotiation); handle_client_hello(Version, #client_hello{session_id = SugesstedId, cipher_suites = CipherSuites, @@ -409,7 +410,7 @@ decode_handshake(Version, ?CLIENT_HELLO, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> Exts = ssl_handshake:decode_vector(Extensions), - DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client), + DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client_hello), #client_hello{ client_version = {Major,Minor}, random = Random, diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 104017b67c..f381e038cf 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -28,13 +28,45 @@ -include("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). +-include("ssl_record.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Encode -export([encode_handshake/1, decode_handshake/2]). %% Handshake --export([handle_client_hello/5]). +-export([handle_client_hello/3]). + +%% Create handshake messages +-export([server_hello/4]). + +%%==================================================================== +%% Create handshake messages +%%==================================================================== + +server_hello(SessionId, KeyShare, ConnectionStates, _Map) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), + Extensions = server_hello_extensions(KeyShare), + #server_hello{server_version = {3,3}, %% legacy_version + cipher_suite = SecParams#security_parameters.cipher_suite, + compression_method = + SecParams#security_parameters.compression_algorithm, + random = SecParams#security_parameters.server_random, + session_id = SessionId, + extensions = Extensions + }. + +server_hello_extensions(KeyShare) -> + SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, + Extensions = #{server_hello_selected_version => SupportedVersions}, + ssl_handshake:add_server_share(Extensions, KeyShare). + + + +%%==================================================================== +%% Encode handshake +%%==================================================================== encode_handshake(#certificate_request_1_3{ certificate_request_context = Context, @@ -68,14 +100,19 @@ encode_handshake(#key_update{request_update = Update}) -> encode_handshake(HandshakeMsg) -> ssl_handshake:encode_handshake(HandshakeMsg, {3,4}). + +%%==================================================================== +%% Decode handshake +%%==================================================================== + decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) -> - Exts = decode_extensions(EncExts), + Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = <<>>, extensions = Exts}; decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary, ?UINT16(Size), EncExts:Size/binary>>) -> - Exts = decode_extensions(EncExts), + Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = Context, extensions = Exts}; @@ -94,12 +131,12 @@ decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary, }; decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) -> #encrypted_extensions{ - extensions = decode_extensions(EncExts) + extensions = decode_extensions(EncExts, encrypted_extensions) }; decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age), ?BYTE(Nonce), ?UINT16(TicketSize), Ticket:TicketSize/binary, BinExts/binary>>) -> - Exts = decode_extensions(BinExts), + Exts = decode_extensions(BinExts, encrypted_extensions), #new_session_ticket{ticket_lifetime = LifeTime, ticket_age_add = Age, ticket_nonce = Nonce, @@ -143,58 +180,271 @@ decode_cert_entries(<<>>, Acc) -> lists:reverse(Acc); decode_cert_entries(<<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary, Rest/binary>>, Acc) -> - Exts = decode_extensions(BinExts), + Exts = decode_extensions(BinExts, certificate_request), decode_cert_entries(Rest, [#certificate_entry{data = Data, extensions = Exts} | Acc]). encode_extensions(Exts)-> ssl_handshake:encode_extensions(extensions_list(Exts)). -decode_extensions(Exts) -> - ssl_handshake:decode_extensions(Exts, {3,4}). +decode_extensions(Exts, MessageType) -> + ssl_handshake:decode_extensions(Exts, {3,4}, MessageType). extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions)]. -handle_client_hello(Version, - #client_hello{session_id = SugesstedId, - cipher_suites = CipherSuites, - compression_methods = Compressions, +%%==================================================================== +%% Handle handshake messages +%%==================================================================== + +handle_client_hello(#client_hello{cipher_suites = ClientCiphers, random = Random, - extensions = HelloExt}, - #ssl_options{versions = Versions, - signature_algs = SupportedHashSigns, - eccs = SupportedECCs, - honor_ecc_order = ECCOrder} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, - Renegotiation) -> - case tls_record:is_acceptable_version(Version, Versions) of - true -> - %% Get supported_groups - %% SupportedGroups = maps:get(elliptic_curves, HelloExt, undefined), - %% Get KeyShareClientHello - - %% Validate supported_groups + KeyShareClientHello - %% IF valid THEN - %% IF supported_groups IS empty send HelloRetryRequest - %% ELSE continue - %% ELSE - %% send Alert - %% ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), - %% ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined), - - %% Implement session handling. - - %% Select curve - - %% Sessions cannot be resumed by ClientHello - - %% Select cipher_suite - %% Select hash_sign - - %% Handle extensions - ok; - false -> - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) + session_id = SessionId, + extensions = Extensions} = _Hello, + #ssl_options{ciphers = ServerCiphers, + signature_algs = ServerSignAlgs, + signature_algs_cert = _SignatureSchemes, %% TODO: Check?? + supported_groups = ServerGroups0} = _SslOpts, + Env) -> + + Cert = maps:get(cert, Env, undefined), + + ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), + ClientGroups = get_supported_groups(ClientGroups0), + ServerGroups = get_supported_groups(ServerGroups0), + + ClientShares0 = maps:get(key_share, Extensions, undefined), + ClientShares = get_key_shares(ClientShares0), + + ClientSignAlgs = get_signature_scheme_list( + maps:get(signature_algs, Extensions, undefined)), + ClientSignAlgsCert = get_signature_scheme_list( + maps:get(signature_algs_cert, Extensions, undefined)), + + %% TODO: use library function if it exists + %% Init the maybe "monad" + {Ref,Maybe} = maybe(), + + try + %% If the server does not select a PSK, then the server independently selects a + %% cipher suite, an (EC)DHE group and key share for key establishment, + %% and a signature algorithm/certificate pair to authenticate itself to + %% the client. + Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), + Group = Maybe(select_server_group(ServerGroups, ClientGroups)), + Maybe(validate_key_share(ClientGroups, ClientShares)), + _ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)), + + %% Handle certificate + {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert), + + %% Check if client supports signature algorithm of server certificate + Maybe(check_cert_sign_algo(SignAlgo, ClientSignAlgs, ClientSignAlgsCert)), + + %% Check if server supports + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), + + %% Generate server_share + KeyShare = ssl_cipher:generate_server_share(Group), + + _Ret = #{cipher => Cipher, + group => Group, + sign_alg => SelectedSignAlg, + %% client_share => ClientPubKey, + key_share => KeyShare, + client_random => Random, + session_id => SessionId} + + %% TODO: + %% - session handling + %% - handle extensions: ALPN + %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats) + + catch + {Ref, {insufficient_security, no_suitable_groups}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); + {Ref, illegal_parameter} -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + {Ref, {client_hello_retry_request, _Group0}} -> + %% TODO + exit({client_hello_retry_request, not_implemented}); + {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. + + +%% 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 +%% "insufficient_security" alert. +select_server_group(_, []) -> + {error, {insufficient_security, no_suitable_groups}}; +select_server_group(ServerGroups, [C|ClientGroups]) -> + case lists:member(C, ServerGroups) of + true -> + {ok, C}; + false -> + select_server_group(ServerGroups, ClientGroups) end. + +%% RFC 8446 - 4.2.8. Key Share +%% This vector MAY be empty if the client is requesting a +%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a +%% group offered in the "supported_groups" extension and MUST appear in +%% the same order. However, the values MAY be a non-contiguous subset +%% of the "supported_groups" extension and MAY omit the most preferred +%% groups. +%% +%% Clients can offer as many KeyShareEntry values as the number of +%% supported groups it is offering, each representing a single set of +%% key exchange parameters. +%% +%% Clients MUST NOT offer multiple KeyShareEntry values +%% for the same group. Clients MUST NOT offer any KeyShareEntry values +%% 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(_ ,[]) -> + ok; +validate_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). + + +get_client_public_key(Group, ClientShares) -> + case lists:keysearch(Group, 2, ClientShares) of + {value, {_, _, ClientPublicKey}} -> + {ok, ClientPublicKey}; + false -> + %% ClientHelloRetryRequest + {error, {client_hello_retry_request, Group}} + end. + +select_cipher_suite([], _) -> + {error, no_suitable_cipher}; +select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> + case lists:member(Cipher, ServerCiphers) of + true -> + {ok, Cipher}; + false -> + select_cipher_suite(ClientCiphers, ServerCiphers) + end. + +%% RFC 8446 (TLS 1.3) +%% TLS 1.3 provides two extensions for indicating which signature +%% algorithms may be used in digital signatures. The +%% "signature_algorithms_cert" extension applies to signatures in +%% certificates and the "signature_algorithms" extension, which +%% originally appeared in TLS 1.2, applies to signatures in +%% CertificateVerify messages. +%% +%% If no "signature_algorithms_cert" extension is +%% present, then the "signature_algorithms" extension also applies to +%% signatures appearing in certificates. +check_cert_sign_algo(SignAlgo, ClientSignAlgs, undefined) -> + maybe_lists_member(SignAlgo, ClientSignAlgs, + {insufficient_security, no_suitable_signature_algorithm}); +check_cert_sign_algo(SignAlgo, _, ClientSignAlgsCert) -> + maybe_lists_member(SignAlgo, ClientSignAlgsCert, + {insufficient_security, no_suitable_signature_algorithm}). + + +%% DSA keys are not supported by TLS 1.3 +select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> + {error, {insufficient_security, no_suitable_public_key}}; +%% TODO: Implement check for ellipctic curves! +select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> + {_, S, _} = ssl_cipher:scheme_to_components(C), + case PublicKeyAlgo =:= rsa andalso + ((S =:= rsa_pkcs1) orelse (S =:= rsa_pss_rsae) orelse (S =:= rsa_pss_pss)) andalso + lists:member(C, ServerSignAlgs) of + true -> + {ok, C}; + false -> + select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs) + end. + + +maybe_lists_member(Elem, List, Error) -> + case lists:member(Elem, List) of + true -> + ok; + false -> + {error, Error} + end. + +%% TODO: test with ecdsa, rsa_pss_rsae, rsa_pss_pss +get_certificate_params(Cert) -> + {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert), + SignAlgo = public_key:pkix_sign_types(SignAlgo0), + PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), + Scheme = sign_algo_to_scheme(SignAlgo), + {PublicKeyAlgo, Scheme}. + +sign_algo_to_scheme({Hash0, Sign0}) -> + SupportedSchemes = tls_v1:default_signature_schemes({3,4}), + Hash = case Hash0 of + sha -> + sha1; + H -> + H + end, + Sign = case Sign0 of + rsa -> + rsa_pkcs1; + S -> + S + end, + sign_algo_to_scheme(Hash, Sign, SupportedSchemes). +%% +sign_algo_to_scheme(_, _, []) -> + not_found; +sign_algo_to_scheme(H, S, [Scheme|T]) -> + {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme), + case H =:= Hash andalso S =:= Sign of + true -> + Scheme; + false -> + sign_algo_to_scheme(H, S, T) + end. + + +%% Note: copied from ssl_handshake +public_key_algo(?rsaEncryption) -> + rsa; +public_key_algo(?'id-ecPublicKey') -> + ecdsa; +public_key_algo(?'id-dsa') -> + dsa. + +get_signature_scheme_list(undefined) -> + undefined; +get_signature_scheme_list(#signature_algorithms_cert{ + signature_scheme_list = ClientSignatureSchemes}) -> + ClientSignatureSchemes; +get_signature_scheme_list(#signature_algorithms{ + signature_scheme_list = ClientSignatureSchemes}) -> + ClientSignatureSchemes. + +get_supported_groups(#supported_groups{supported_groups = Groups}) -> + Groups. + +get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> + ClientShares. + +maybe() -> + Ref = erlang:make_ref(), + Ok = fun(ok) -> ok; + ({ok,R}) -> R; + ({error,Reason}) -> + throw({Ref,Reason}) + end, + {Ref,Ok}. diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 9ee0e0f845..6ef5364399 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -59,7 +59,7 @@ key_exchange %key_exchange<1..2^16-1>; }). -record(key_share_client_hello, { - entries %% KeyShareEntry client_shares<0..2^16-1>; + client_shares %% KeyShareEntry client_shares<0..2^16-1>; }). -record(key_share_hello_retry_request, { selected_group %% NamedGroup diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 7debac7d37..ed7b475619 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -47,7 +47,7 @@ -export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2, highest_protocol_version/1, highest_protocol_version/2, is_higher/2, supported_protocol_versions/0, - is_acceptable_version/1, is_acceptable_version/2, hello_version/2]). + is_acceptable_version/1, is_acceptable_version/2, hello_version/1]). -export_type([tls_version/0, tls_atom_version/0]). @@ -116,7 +116,7 @@ encode_handshake(Frag, Version, ConnectionStates) -> case iolist_size(Frag) of N when N > ?MAX_PLAIN_TEXT_LENGTH -> - Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), + Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation), encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) @@ -157,7 +157,7 @@ encode_data(Frag, Version, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), + Data = split_bin(Frag, Version, BCA, BeastMitigation), encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). %%==================================================================== @@ -386,10 +386,10 @@ is_acceptable_version({N,_} = Version, Versions) is_acceptable_version(_,_) -> false. --spec hello_version(tls_version(), [tls_version()]) -> tls_version(). -hello_version(Version, _) when Version >= {3, 3} -> - Version; -hello_version(_, Versions) -> +-spec hello_version([tls_version()]) -> tls_version(). +hello_version([Highest|_]) when Highest >= {3,3} -> + Highest; +hello_version(Versions) -> lowest_protocol_version(Versions). %%-------------------------------------------------------------------- @@ -526,27 +526,26 @@ start_additional_data(Type, {MajVer, MinVer}, %% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are %% not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when +split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse {3, 0} == Version) -> - do_split_bin(Rest, ChunkSize, [[FirstByte]]); + [[FirstByte]|do_split_bin(Rest)]; %% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 %% splitting. -split_bin(Bin, ChunkSize, Version, BCA, zero_n) when +split_bin(Bin, Version, BCA, zero_n) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse {3, 0} == Version) -> - do_split_bin(Bin, ChunkSize, [[<<>>]]); -split_bin(Bin, ChunkSize, _, _, _) -> - do_split_bin(Bin, ChunkSize, []). + [<<>>|do_split_bin(Bin)]; +split_bin(Bin, _, _, _) -> + do_split_bin(Bin). -do_split_bin(<<>>, _, Acc) -> - lists:reverse(Acc); -do_split_bin(Bin, ChunkSize, Acc) -> +do_split_bin(<<>>) -> []; +do_split_bin(Bin) -> case Bin of - <<Chunk:ChunkSize/binary, Rest/binary>> -> - do_split_bin(Rest, ChunkSize, [Chunk | Acc]); + <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> -> + [Chunk|do_split_bin(Rest)]; _ -> - lists:reverse(Acc, [Bin]) + [Bin] end. %%-------------------------------------------------------------------- lowest_list_protocol_version(Ver, []) -> diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index ff198a09bf..d424336187 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -111,21 +111,32 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, cipher_type = ?AEAD, bulk_cipher_algorithm = BulkCipherAlgo} - } = ReadState0} = ConnnectionStates0) -> + } = ReadState0} = ConnectionStates0) -> AAD = start_additional_data(), CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0), case decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment) of {PlainFragment, CipherS1} -> - ConnnectionStates = - ConnnectionStates0#{current_read => + ConnectionStates = + ConnectionStates0#{current_read => ReadState0#{cipher_state => CipherS1, sequence_number => Seq + 1}}, - decode_inner_plaintext(PlainFragment, ConnnectionStates); + decode_inner_plaintext(PlainFragment, ConnectionStates); #alert{} = Alert -> Alert end; +decode_cipher_text(#ssl_tls{type = Type, + version = ?LEGACY_VERSION, + fragment = CipherFragment}, + #{current_read := + #{security_parameters := + #security_parameters{ + cipher_suite = ?TLS_NULL_WITH_NULL_NULL} + }} = ConnnectionStates0) -> + {#ssl_tls{type = Type, + version = {3,4}, %% Internally use real version + fragment = CipherFragment}, ConnnectionStates0}; decode_cipher_text(#ssl_tls{type = Type}, _) -> - %% Version mismatch is already asserted + %% Version mismatch is already asserted ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_typ_mismatch, Type}). %%-------------------------------------------------------------------- @@ -169,7 +180,23 @@ encode_plain_text(#inner_plaintext{ AAD = start_additional_data(), CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0), {Encoded, WriteState} = cipher_aead(PlainText, WriteState0#{cipher_state => CipherS1}, AAD), - {#tls_cipher_text{encoded_record = Encoded}, WriteState}; + {#tls_cipher_text{opaque_type = Type, + legacy_version = {3,3}, + encoded_record = Encoded}, WriteState}; +encode_plain_text(#inner_plaintext{ + content = Data, + type = Type + }, #{security_parameters := + #security_parameters{ + cipher_suite = ?TLS_NULL_WITH_NULL_NULL} + } = WriteState0) -> + %% RFC8446 - 5.1. Record Layer + %% When record protection has not yet been engaged, TLSPlaintext + %% structures are written directly onto the wire. + {#tls_cipher_text{opaque_type = Type, + legacy_version = {3,3}, + encoded_record = Data}, WriteState0}; + encode_plain_text(_, CS) -> exit({cs, CS}). diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 75409143a8..0631ed808a 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -103,7 +103,7 @@ send_alert(Pid, Alert) -> %% in the connection state and recive an ack. %%-------------------------------------------------------------------- send_and_ack_alert(Pid, Alert) -> - gen_statem:cast(Pid, {ack_alert, Alert}). + gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT). %%-------------------------------------------------------------------- -spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}. %% Description: Send application data @@ -203,8 +203,9 @@ connection({call, From}, renegotiate, #data{connection_states = #{current_write := Write}} = StateData) -> {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; connection({call, From}, {application_data, AppData}, - #data{socket_options = SockOpts} = StateData) -> - case encode_packet(AppData, SockOpts) of + #data{socket_options = #socket_options{packet = Packet}} = + StateData) -> + case encode_packet(Packet, AppData) of {error, _} = Error -> {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; Data -> @@ -220,17 +221,30 @@ connection({call, From}, dist_get_tls_socket, tracker = Tracker} = StateData) -> TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker), {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; -connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{connection_pid = Pid} = StateData) -> +connection({call, From}, {dist_handshake_complete, _Node, DHandle}, + #data{connection_pid = Pid, + socket_options = #socket_options{packet = Packet}} = + StateData) -> ok = erlang:dist_ctrl_input_handler(DHandle, Pid), ok = ssl_connection:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), - Events = dist_data_events(DHandle, []), - {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, [{reply, From, ok} | Events]}; -connection(cast, {ack_alert, #alert{} = Alert}, #data{connection_pid = Pid} =StateData0) -> + {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, + [{reply, From, ok} + | case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end]}; +connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> StateData = send_tls_alert(Alert, StateData0), - Pid ! {self(), ack_alert}, - {next_state, ?FUNCTION_NAME, StateData}; + {next_state, ?FUNCTION_NAME, StateData, + [{reply,From,ok}]}; +connection(internal, {application_packets, From, Data}, StateData) -> + send_application_data(Data, From, ?FUNCTION_NAME, StateData); +%% connection(cast, #alert{} = Alert, StateData0) -> StateData = send_tls_alert(Alert, StateData0), {next_state, ?FUNCTION_NAME, StateData}; @@ -240,9 +254,19 @@ connection(cast, {new_write, WritesState, Version}, StateData#data{connection_states = ConnectionStates0#{current_write => WritesState}, negotiated_version = Version}}; -connection(info, dist_data, #data{dist_handle = DHandle} = StateData) -> - Events = dist_data_events(DHandle, []), - {next_state, ?FUNCTION_NAME, StateData, Events}; +%% +connection(info, dist_data, + #data{dist_handle = DHandle, + socket_options = #socket_options{packet = Packet}} = + StateData) -> + {next_state, ?FUNCTION_NAME, StateData, + case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end}; connection(info, tick, StateData) -> consume_ticks(), {next_state, ?FUNCTION_NAME, StateData, @@ -275,6 +299,8 @@ handshake(cast, {new_write, WritesState, Version}, StateData#data{connection_states = ConnectionStates0#{current_write => WritesState}, negotiated_version = Version}}; +handshake(internal, {application_packets,_,_}, _) -> + {keep_state_and_data, [postpone]}; handshake(info, Msg, StateData) -> handle_info(Msg, ?FUNCTION_NAME, StateData). @@ -351,12 +377,13 @@ send_application_data(Data, From, StateName, log_level = LogLevel} = StateData0) -> case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of true -> - ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + ssl_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, - [{next_event, {call, From}, {application_data, Data}}]}; + [{next_event, internal, {application_packets, From, Data}}]}; false -> {Msgs, ConnectionStates} = - Connection:encode_data(Data, Version, ConnectionStates0), + Connection:encode_data( + iolist_to_binary(Data), Version, ConnectionStates0), StateData = StateData0#data{connection_states = ConnectionStates}, case Connection:send(Transport, Socket, Msgs) of ok when DistHandle =/= undefined -> @@ -378,21 +405,18 @@ send_application_data(Data, From, StateName, end end. -encode_packet(Data, #socket_options{packet=Packet}) -> +-compile({inline, encode_packet/2}). +encode_packet(Packet, Data) -> + Len = iolist_size(Data), case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. - -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> - {error, {badarg, {packet_to_large, Len, Max}}}; - false -> - <<Len:Size, Bin/binary>> + 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data]; + 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data]; + 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data]; + N when N =:= 1; N =:= 2; N =:= 4 -> + {error, + {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}}; + _ -> + Data end. set_opts(SocketOptions, [{packet, N}]) -> @@ -426,14 +450,18 @@ call(FsmPid, Event) -> %%---------------Erlang distribution -------------------------------------- -dist_data_events(DHandle, Events) -> +dist_data(DHandle, Packet) -> case erlang:dist_ctrl_get_data(DHandle) of none -> erlang:dist_ctrl_get_data_notification(DHandle), - lists:reverse(Events); + []; Data -> - Event = {next_event, {call, {self(), undefined}}, {application_data, Data}}, - dist_data_events(DHandle, [Event | Events]) + %% This is encode_packet(4, Data) without Len check + %% since the emulator will always deliver a Data + %% smaller than 4 GB, and the distribution will + %% therefore always have to use {packet,4} + Len = iolist_size(Data), + [<<Len:32>>,Data|dist_data(DHandle, Packet)] end. consume_ticks() -> diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 68ba598612..83dd7585dd 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -34,7 +34,7 @@ ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, default_signature_algs/1, signature_algs/2, default_signature_schemes/1, signature_schemes/2, - groups/1, groups/2, group_to_enum/1, enum_to_group/1]). + groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]). -export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4]). @@ -346,8 +346,8 @@ signature_algs({3, 3}, HashSigns) -> end, [], HashSigns), lists:reverse(Supported). -default_signature_algs({3, 4}) -> - default_signature_algs({3, 3}); +default_signature_algs({3, 4} = Version) -> + default_signature_schemes(Version); default_signature_algs({3, 3} = Version) -> Default = [%% SHA2 {sha512, ecdsa}, @@ -551,7 +551,7 @@ ecc_curves(_Minor, TLSCurves) -> end end, [], TLSCurves). --spec groups(4 | all) -> [group()]. +-spec groups(4 | all | default) -> [group()]. groups(all) -> [secp256r1, secp384r1, @@ -561,6 +561,11 @@ groups(all) -> ffdhe4096, ffdhe6144, ffdhe8192]; +groups(default) -> + [secp256r1, + secp384r1, + secp521r1, + ffdhe2048]; groups(Minor) -> TLSGroups = groups(all), groups(Minor, TLSGroups). @@ -571,6 +576,10 @@ groups(_Minor, TLSGroups) -> CryptoGroups = crypto:ec_curves() ++ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192], lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups). +default_groups(Minor) -> + TLSGroups = groups(default), + groups(Minor, TLSGroups). + group_to_enum(secp256r1) -> 23; group_to_enum(secp384r1) -> 24; group_to_enum(secp521r1) -> 25; |