From 38b07caa2a1c6cd3537eadd36770afa54f067562 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 7 Nov 2017 18:34:34 +0100 Subject: ssl: Countermeasurements for Bleichenbacher attack --- lib/ssl/src/dtls_connection.erl | 1 + lib/ssl/src/ssl_connection.erl | 21 +++++++++++++++++++-- lib/ssl/src/ssl_connection.hrl | 1 + lib/ssl/src/tls_connection.erl | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index ff3e69bae5..260ae8769c 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -644,6 +644,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State = prepare_flight(State0#state{connection_states = ConnectionStates, negotiated_version = Version, + client_hello_version = ClientVersion, hashsign_algorithm = HashSign, session = Session, negotiated_protocol = Protocol}), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index b031d3d47b..b33ce63c32 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1367,8 +1367,25 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{private_key = Key} = State, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key), + #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) -> + + %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret + %% and fail handshake later.RFC 5246 section 7.4.7.1. + PremasterSecret = + try ssl_handshake:premaster_secret(EncPMS, Key) of + Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> + case Secret of + <> -> %% Correct + Secret; + <> -> %% Version mismatch + <> + end; + _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES + make_premaster_secret(Version, rsa) + catch + #alert{description = ?DECRYPT_ERROR} -> + make_premaster_secret(Version, rsa) + end, calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 3e26f67de1..f9d2149170 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -57,6 +57,7 @@ session_cache_cb :: atom(), crl_db :: term(), negotiated_version :: ssl_record:ssl_version() | 'undefined', + client_hello_version :: ssl_record:ssl_version() | 'undefined', client_certificate_requested = false :: boolean(), key_algorithm :: ssl_cipher:key_algo(), hashsign_algorithm = {undefined, undefined}, diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index e3ffbea3d3..7861287a22 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -286,6 +286,7 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, gen_handshake(ssl_connection, hello, internal, {common_client_hello, Type, ServerHelloExt}, State#state{connection_states = ConnectionStates, negotiated_version = Version, + client_hello_version = ClientVersion, hashsign_algorithm = HashSign, session = Session, negotiated_protocol = Protocol}) -- cgit v1.2.3 From 90b1b43eb3be556d47cecbe766d6bff3761be31e Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 9 Nov 2017 16:53:44 +0100 Subject: ssl: Prepare for release --- lib/ssl/vsn.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index bb77326751..cf6481d14c 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 8.2.1 +SSL_VSN = 8.2.2 -- cgit v1.2.3 From 4147cfeb7c068cc65831d3fc249f6d4a83830a58 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 13 Oct 2017 13:29:05 +0200 Subject: ssl: Use ?FUNCTION_NAME Use ?FUNCTION_NAME macro to enhance code as we will not back-port this version of the ssl application to versions pre OTP 19. --- lib/ssl/src/dtls_connection.erl | 82 +++++++++++++++++----------------- lib/ssl/src/ssl_connection.erl | 98 ++++++++++++++++++++--------------------- lib/ssl/src/tls_connection.erl | 28 ++++++------ 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index ff3e69bae5..2c5cfb0c5e 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -273,28 +273,28 @@ init({call, From}, {start, Timeout}, {Record, State} = next_record(State3), next_event(hello, Record, State, Actions); init({call, _} = Type, Event, #state{role = server, transport_cb = gen_udp} = State) -> - Result = ssl_connection:init(Type, Event, - State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, - protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), - previous_cookie_secret => <<>>, - ignored_alerts => 0, - max_ignored_alerts => 10}}, - ?MODULE), + Result = ssl_connection:?FUNCTION_NAME(Type, Event, + State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => <<>>, + ignored_alerts => 0, + max_ignored_alerts => 10}}, + ?MODULE), 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 - ssl_connection:init(Type, Event, State#state{flight_state = reliable}, ?MODULE); + ssl_connection:?FUNCTION_NAME(Type, Event, State#state{flight_state = reliable}, ?MODULE); init(Type, Event, State) -> - ssl_connection:init(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). error(enter, _, State) -> {keep_state, State}; error({call, From}, {start, _Timeout}, {Error, State}) -> {stop_and_reply, normal, {reply, From, {error, Error}}, State}; error({call, From}, Msg, State) -> - handle_call(Msg, From, error, State); + handle_call(Msg, From, ?FUNCTION_NAME, State); error(_, _, _) -> {keep_state_and_data, [postpone]}. @@ -326,7 +326,7 @@ hello(internal, #client_hello{cookie = <<>>, State1 = prepare_flight(State0#state{negotiated_version = Version}), {State2, Actions} = send_handshake(VerifyRequest, State1), {Record, State} = next_record(State2), - next_event(hello, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); + next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server, transport_cb = Transport, socket = Socket, @@ -367,7 +367,7 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, Session0#session{session_id = Hello#client_hello.session_id}}, {Record, State} = next_record(State3), - next_event(hello, Record, State, Actions); + next_event(?FUNCTION_NAME, Record, State, Actions); hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, @@ -376,80 +376,80 @@ hello(internal, #server_hello{} = Hello, ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State); + handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> %% Initial hello should not be in handshake history - {next_state, hello, State, [{next_event, internal, Handshake}]}; + {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> %% hello_verify should not be in handshake history - {next_state, hello, State, [{next_event, internal, Handshake}]}; + {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; hello(info, Event, State) -> - handle_info(Event, hello, State); + handle_info(Event, ?FUNCTION_NAME, State); hello(state_timeout, Event, State) -> - handle_state_timeout(Event, hello, State); + handle_state_timeout(Event, ?FUNCTION_NAME, State); hello(Type, Event, State) -> - ssl_connection:hello(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). abbreviated(enter, _, State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; abbreviated(info, Event, State) -> - handle_info(Event, abbreviated, State); + handle_info(Event, ?FUNCTION_NAME, State); abbreviated(internal = Type, #change_cipher_spec{type = <<1>>} = Event, #state{connection_states = ConnectionStates0} = State) -> ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), - ssl_connection:abbreviated(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); + ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> - ssl_connection:abbreviated(Type, Event, - prepare_flight(State#state{connection_states = ConnectionStates, - flight_state = connection}), ?MODULE); + ssl_connection:?FUNCTION_NAME(Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + flight_state = connection}), ?MODULE); abbreviated(state_timeout, Event, State) -> - handle_state_timeout(Event, abbreviated, State); + handle_state_timeout(Event, ?FUNCTION_NAME, State); abbreviated(Type, Event, State) -> - ssl_connection:abbreviated(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). certify(enter, _, State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; certify(info, Event, State) -> - handle_info(Event, certify, State); + handle_info(Event, ?FUNCTION_NAME, State); certify(internal = Type, #server_hello_done{} = Event, State) -> ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); certify(state_timeout, Event, State) -> - handle_state_timeout(Event, certify, State); + handle_state_timeout(Event, ?FUNCTION_NAME, State); certify(Type, Event, State) -> - ssl_connection:certify(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). cipher(enter, _, State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; cipher(info, Event, State) -> - handle_info(Event, cipher, State); + handle_info(Event, ?FUNCTION_NAME, State); cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, #state{connection_states = ConnectionStates0} = State) -> ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), - ssl_connection:cipher(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); + ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> - ssl_connection:cipher(Type, Event, - prepare_flight(State#state{connection_states = ConnectionStates, - flight_state = connection}), - ?MODULE); + ssl_connection:?FUNCTION_NAME(Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + flight_state = connection}), + ?MODULE); cipher(state_timeout, Event, State) -> - handle_state_timeout(Event, cipher, State); + handle_state_timeout(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - ssl_connection:cipher(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). connection(enter, _, State) -> {keep_state, State}; connection(info, Event, State) -> - handle_info(Event, connection, State); + handle_info(Event, ?FUNCTION_NAME, State); connection(internal, #hello_request{}, #state{host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, @@ -476,15 +476,15 @@ connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), State1 = send_alert(Alert, State0), {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), - next_event(connection, Record, State); + next_event(?FUNCTION_NAME, Record, State); connection(Type, Event, State) -> - ssl_connection:connection(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). %%TODO does this make sense for DTLS ? downgrade(enter, _, State) -> {keep_state, State}; downgrade(Type, Event, State) -> - ssl_connection:downgrade(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- %% Description: This function is called by a gen_fsm when it receives any diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index b031d3d47b..c6f67eb696 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -366,7 +366,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout}, {stop_and_reply, normal, {reply, From, {error, Error}}} end; init({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, init, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); init(_Type, _Event, _State, _Connection) -> {keep_state_and_data, [postpone]}. @@ -377,13 +377,13 @@ init(_Type, _Event, _State, _Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- hello({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, hello, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> do_server_hello(Type, ServerHelloExt, State, Connection); hello(info, Msg, State, _) -> - handle_info(Msg, hello, State); + handle_info(Msg, ?FUNCTION_NAME, State); hello(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, hello, State, Connection). + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), @@ -392,7 +392,7 @@ hello(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- abbreviated({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, abbreviated, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); abbreviated(internal, #finished{verify_data = Data} = Finished, #state{role = server, @@ -412,7 +412,7 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, expecting_finished = false}, Connection), Connection:next_event(connection, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; abbreviated(internal, #finished{verify_data = Data} = Finished, @@ -428,11 +428,11 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), {State1, Actions} = finalize_handshake(State0#state{connection_states = ConnectionStates1}, - abbreviated, Connection), + ?FUNCTION_NAME, Connection), {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), Connection:next_event(connection, Record, State, Actions); #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; %% only allowed to send next_protocol message after change cipher spec @@ -442,7 +442,7 @@ abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, Connection) -> {Record, State} = Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_event(abbreviated, Record, + Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_next_protocol_negotiation = false}); abbreviated(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = @@ -451,11 +451,11 @@ abbreviated(internal, ssl_record:activate_pending_connection_state(ConnectionStates0, read), {Record, State} = Connection:next_record(State0#state{connection_states = ConnectionStates1}), - Connection:next_event(abbreviated, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); abbreviated(info, Msg, State, _) -> - handle_info(Msg, abbreviated, State); + handle_info(Msg, ?FUNCTION_NAME, State); abbreviated(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, abbreviated, State, Connection). + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), @@ -465,16 +465,16 @@ abbreviated(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- certify({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, certify, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); certify(info, Msg, State, _) -> - handle_info(Msg, certify, State); + handle_info(Msg, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, #state{role = server, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - handle_own_alert(Alert, Version, certify, State); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, #state{role = server, @@ -483,7 +483,7 @@ certify(internal, #certificate{asn1_certificates = []}, State0, Connection) -> {Record, State} = Connection:next_record(State0#state{client_certificate_requested = false}), - Connection:next_event(certify, Record, State); + Connection:next_event(?FUNCTION_NAME, Record, State); certify(internal, #certificate{}, #state{role = server, @@ -491,7 +491,7 @@ certify(internal, #certificate{}, ssl_options = #ssl_options{verify = verify_none}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), - handle_own_alert(Alert, Version, certify, State); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{} = Cert, #state{negotiated_version = Version, @@ -506,7 +506,7 @@ certify(internal, #certificate{} = Cert, handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; certify(internal, #server_key_exchange{exchange_keys = Keys}, @@ -538,7 +538,7 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, Connection); false -> handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), - Version, certify, State) + Version, ?FUNCTION_NAME, State) end end; @@ -549,10 +549,10 @@ certify(internal, #certificate_request{} = CertRequest, negotiated_version = Version} = State0, Connection) -> case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of #alert {} = Alert -> - handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); NegotiatedHashSign -> {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), - Connection:next_event(certify, Record, + Connection:next_event(?FUNCTION_NAME, Record, State#state{cert_hashsign_algorithm = NegotiatedHashSign}) end; @@ -568,7 +568,7 @@ certify(internal, #server_hello_done{}, when Alg == psk -> case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); PremasterSecret -> State = master_secret(PremasterSecret, State0#state{premaster_secret = PremasterSecret}), @@ -589,7 +589,7 @@ certify(internal, #server_hello_done{}, case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); PremasterSecret -> State = master_secret(PremasterSecret, State0#state{premaster_secret = RSAPremasterSecret}), @@ -609,7 +609,7 @@ certify(internal, #server_hello_done{}, State = State0#state{connection_states = ConnectionStates}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; %% Master secret is calculated from premaster_secret @@ -627,7 +627,7 @@ certify(internal, #server_hello_done{}, session = Session}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; certify(internal = Type, #client_key_exchange{} = Msg, @@ -636,7 +636,7 @@ certify(internal = Type, #client_key_exchange{} = Msg, ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) -> %% We expect a certificate here - handle_common_event(Type, Msg, certify, State, Connection); + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); certify(internal, #client_key_exchange{exchange_keys = Keys}, State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) -> @@ -645,11 +645,11 @@ certify(internal, #client_key_exchange{exchange_keys = Keys}, State, Connection) catch #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; certify(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, certify, State, Connection). + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), @@ -658,10 +658,10 @@ certify(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- cipher({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, cipher, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); cipher(info, Msg, State, _) -> - handle_info(Msg, cipher, State); + handle_info(Msg, ?FUNCTION_NAME, State); cipher(internal, #certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, @@ -680,10 +680,10 @@ cipher(internal, #certificate_verify{signature = Signature, TLSVersion, HashSign, MasterSecret, Handshake) of valid -> {Record, State} = Connection:next_record(State0), - Connection:next_event(cipher, Record, + Connection:next_event(?FUNCTION_NAME, Record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; %% client must send a next protocol message if we are expecting it @@ -691,7 +691,7 @@ cipher(internal, #finished{}, #state{role = server, expecting_next_protocol_negotiation = true, negotiated_protocol = undefined, negotiated_version = Version} = State0, _Connection) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); + handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); cipher(internal, #finished{verify_data = Data} = Finished, #state{negotiated_version = Version, @@ -712,7 +712,7 @@ cipher(internal, #finished{verify_data = Data} = Finished, cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; %% only allowed to send next_protocol message after change cipher spec @@ -722,7 +722,7 @@ cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, expecting_finished = true} = State0, Connection) -> {Record, State} = Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_event(cipher, Record, + Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_next_protocol_negotiation = false}); cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = State0, Connection) -> @@ -730,9 +730,9 @@ cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ssl_record:activate_pending_connection_state(ConnectionStates0, read), {Record, State} = Connection:next_record(State0#state{connection_states = ConnectionStates1}), - Connection:next_event(cipher, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); cipher(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, cipher, State, Connection). + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), term(), @@ -747,7 +747,7 @@ connection({call, From}, {application_data, Data}, try write_application_data(Data, From, State) catch throw:Error -> - hibernate_after(connection, State, [{reply, From, Error}]) + hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) end; connection({call, RecvFrom}, {recv, N, Timeout}, #state{protocol_cb = Connection, socket_options = @@ -755,34 +755,34 @@ connection({call, RecvFrom}, {recv, N, Timeout}, Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), Connection:passive_receive(State0#state{bytes_to_read = N, start_or_recv_from = RecvFrom, - timer = Timer}, connection); + timer = Timer}, ?FUNCTION_NAME); connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, Connection) -> Connection:renegotiate(State#state{renegotiation = {true, From}}, []); connection({call, From}, peer_certificate, #state{session = #session{peer_certificate = Cert}} = State, _) -> - hibernate_after(connection, State, [{reply, From, {ok, Cert}}]); + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); connection({call, From}, {connection_information, true}, State, _) -> Info = connection_info(State) ++ security_info(State), - hibernate_after(connection, State, [{reply, From, {ok, Info}}]); + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); connection({call, From}, {connection_information, false}, State, _) -> Info = connection_info(State), - hibernate_after(connection, State, [{reply, From, {ok, Info}}]); + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); connection({call, From}, negotiated_protocol, #state{negotiated_protocol = undefined} = State, _) -> - hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]); + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); connection({call, From}, negotiated_protocol, #state{negotiated_protocol = SelectedProtocol} = State, _) -> - hibernate_after(connection, State, + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, connection, State, Connection); + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); connection(info, Msg, State, _) -> - handle_info(Msg, connection, State); + handle_info(Msg, ?FUNCTION_NAME, State); connection(internal, {recv, _}, State, Connection) -> - Connection:passive_receive(State, connection); + Connection:passive_receive(State, ?FUNCTION_NAME); connection(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, connection, State, Connection). + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), @@ -800,7 +800,7 @@ downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> gen_statem:reply(From, {error, timeout}), {stop, normal, State}; downgrade(Type, Event, State, Connection) -> - handle_common_event(Type, Event, downgrade, State, Connection). + handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection). %%-------------------------------------------------------------------- %% Event handling functions called by state functions to handle diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index e3ffbea3d3..ed86478008 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -241,7 +241,7 @@ init({call, From}, {start, Timeout}, {Record, State} = next_record(State1), next_event(hello, Record, State); init(Type, Event, State) -> - gen_handshake(ssl_connection, init, Type, Event, State). + gen_handshake(ssl_connection, ?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec error(gen_statem:event_type(), @@ -252,7 +252,7 @@ init(Type, Event, State) -> error({call, From}, {start, _Timeout}, {Error, State}) -> {stop_and_reply, normal, {reply, From, {error, Error}}, State}; error({call, From}, Msg, State) -> - handle_call(Msg, From, error, State); + handle_call(Msg, From, ?FUNCTION_NAME, State); error(_, _, _) -> {keep_state_and_data, [postpone]}. @@ -304,36 +304,36 @@ hello(internal, #server_hello{} = Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; hello(info, Event, State) -> - gen_info(Event, hello, State); + gen_info(Event, ?FUNCTION_NAME, State); hello(Type, Event, State) -> - gen_handshake(ssl_connection, hello, Type, Event, State). + gen_handshake(ssl_connection, ?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- abbreviated(info, Event, State) -> - gen_info(Event, abbreviated, State); + gen_info(Event, ?FUNCTION_NAME, State); abbreviated(Type, Event, State) -> - gen_handshake(ssl_connection, abbreviated, Type, Event, State). + gen_handshake(ssl_connection, ?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- certify(info, Event, State) -> - gen_info(Event, certify, State); + gen_info(Event, ?FUNCTION_NAME, State); certify(Type, Event, State) -> - gen_handshake(ssl_connection, certify, Type, Event, State). + gen_handshake(ssl_connection, ?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- cipher(info, Event, State) -> - gen_info(Event, cipher, State); + gen_info(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - gen_handshake(ssl_connection, cipher, Type, Event, State). + gen_handshake(ssl_connection, ?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -341,7 +341,7 @@ cipher(Type, Event, State) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection(info, Event, State) -> - gen_info(Event, connection, State); + gen_info(Event, ?FUNCTION_NAME, State); connection(internal, #hello_request{}, #state{role = client, host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, @@ -373,16 +373,16 @@ connection(internal, #client_hello{}, Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), State1 = send_alert(Alert, State0), {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), - next_event(connection, Record, State); + next_event(?FUNCTION_NAME, Record, State); connection(Type, Event, State) -> - ssl_connection:connection(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- downgrade(Type, Event, State) -> - ssl_connection:downgrade(Type, Event, State, ?MODULE). + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- %% Event handling functions called by state functions to handle -- cgit v1.2.3 From 48faad936ff6189daf9c0af7b39a86400057cb13 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 6 Oct 2017 17:15:52 +0200 Subject: public_key, ssl: Handles keys so that APIs are preserved correctly --- lib/public_key/src/pubkey_cert.erl | 13 ++++++++++--- lib/ssl/test/x509_test.erl | 15 ++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index 13833830a7..76fd0f8133 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -1144,7 +1144,7 @@ issuer(Contact, Role, Name) -> subject(Contact, Role ++ Name). subject(Contact, Name) -> - Opts = [{email, Contact ++ "@erlang.org"}, + Opts = [{email, Contact ++ "@example.org"}, {name, Name}, {city, "Stockholm"}, {country, "SE"}, @@ -1223,12 +1223,12 @@ cert_chain(Role, IssuerCert, IssuerKey, [PeerOpts], _, Acc) -> Key = gen_key(proplists:get_value(key, PeerOpts, default_key_gen())), Cert = cert(Role, public_key:pkix_decode_cert(IssuerCert, otp), IssuerKey, Key, "admin", " Peer cert", PeerOpts, peer), - [{Cert, Key}, {IssuerCert, IssuerKey} | Acc]; + [{Cert, encode_key(Key)}, {IssuerCert, encode_key(IssuerKey)} | Acc]; cert_chain(Role, IssuerCert, IssuerKey, [CAOpts | Rest], N, Acc) -> Key = gen_key(proplists:get_value(key, CAOpts, default_key_gen())), Cert = cert(Role, public_key:pkix_decode_cert(IssuerCert, otp), IssuerKey, Key, "webadmin", " Intermidiate CA " ++ integer_to_list(N), CAOpts, ca), - cert_chain(Role, Cert, Key, Rest, N+1, [{IssuerCert, IssuerKey} | Acc]). + cert_chain(Role, Cert, Key, Rest, N+1, [{IssuerCert, encode_key(IssuerKey)} | Acc]). cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Issuer}}, PrivKey, Key, Contact, Name, Opts, Type) -> @@ -1311,3 +1311,10 @@ add_default_extensions(Defaults0, Exts) -> end, Defaults0), Exts ++ Defaults. +encode_key(#'RSAPrivateKey'{} = Key) -> + {'RSAPrivateKey', public_key:der_encode('RSAPrivateKey', Key)}; +encode_key(#'ECPrivateKey'{} = Key) -> + {'ECPrivateKey', public_key:der_encode('ECPrivateKey', Key)}; +encode_key(#'DSAPrivateKey'{} = Key) -> + {'DSAPrivateKey', public_key:der_encode('DSAPrivateKey', Key)}. + diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl index 031fad1216..fea01efdaf 100644 --- a/lib/ssl/test/x509_test.erl +++ b/lib/ssl/test/x509_test.erl @@ -64,15 +64,12 @@ do_gen_pem_config_files(Config, CertFile, KeyFile, CAFile) -> cert_entry(Cert) -> {'Certificate', Cert, not_encrypted}. -key_entry(Key = #'RSAPrivateKey'{}) -> - Der = public_key:der_encode('RSAPrivateKey', Key), - {'RSAPrivateKey', Der, not_encrypted}; -key_entry(Key = #'DSAPrivateKey'{}) -> - Der = public_key:der_encode('DSAPrivateKey', Key), - {'DSAPrivateKey', Der, not_encrypted}; -key_entry(Key = #'ECPrivateKey'{}) -> - Der = public_key:der_encode('ECPrivateKey', Key), - {'ECPrivateKey', Der, not_encrypted}. +key_entry({'RSAPrivateKey', DERKey}) -> + {'RSAPrivateKey', DERKey, not_encrypted}; +key_entry({'DSAPrivateKey', DERKey}) -> + {'DSAPrivateKey', DERKey, not_encrypted}; +key_entry({'ECPrivateKey', DERKey}) -> + {'ECPrivateKey', DERKey, not_encrypted}. ca_entries(CAs) -> [{'Certificate', CACert, not_encrypted} || CACert <- CAs]. -- cgit v1.2.3 From 3da1637b5ec4f24787d473fa3031bed44958136e Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 6 Oct 2017 17:24:16 +0200 Subject: ssl: Extend hostname check to fallback to checking IP-address If no SNI is available and the hostname is an IP-address also check for IP-address match. This check is not as good as a DNS hostname check and certificates using IP-address are not recommended. --- lib/ssl/doc/src/ssl.xml | 52 ++++++++++++++------- lib/ssl/src/ssl.erl | 2 +- lib/ssl/src/ssl_certificate.erl | 38 ++++++++++++--- lib/ssl/src/ssl_connection.erl | 3 +- lib/ssl/src/ssl_handshake.erl | 18 ++++++-- lib/ssl/test/ssl_sni_SUITE.erl | 100 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 182 insertions(+), 31 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index ca2dcbb761..e80fd59a7f 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -589,22 +589,19 @@ fun(srp, Username :: string(), UserState :: term()) -> {server_name_indication, HostName :: hostname()}

Specify the hostname to be used in TLS Server Name Indication extension. - Is usefull when upgrading a TCP socket to a TLS socket or if the hostname can not be - derived from the Host argument to ssl:connect/3. - Will also cause the client to preform host name verification of the peer certificate - public_key:pkix_verify_hostname(PeerCert, [{dns_id, HostName}]) -

during the x509-path validation. If the check fails the error {bad_cert, hostname_check_failiure} will be - propagated to the path validation fun verify_fun -
- - {server_name_indication, disable} - -

When starting a TLS connection without upgrade, the Server Name - Indication extension is sent if possible that is can be derived from the Host argument - to ssl:connect/3. - This option can be used to disable that behavior.

-

Note that this also disables the default host name verification check of the peer certificate.

+ If not specified it will default to the Host argument of connect/[3,4] + unless it is of type inet:ipaddress().

+

+ The HostName will also be used in the hostname verification of the peer certificate using + public_key:pkix_verify_hostname/2. +

+ {server_name_indication, disable} + +

Prevents the Server Name Indication extension from being sent and + disables the hostname verification check + public_key:pkix_verify_hostname/2

+
{fallback, boolean()}

Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade. @@ -881,6 +878,12 @@ fun(srp, Username :: string(), UserState :: term()) ->

Upgrades a gen_tcp, or equivalent, connected socket to an SSL socket, that is, performs the client-side ssl handshake.

+ +

If the option verify is set to verify_peer + the option server_name_indication shall also be specified, + if it is not no Server Name Indication extension will be sent, + and public_key:pkix_verify_hostname/2 + will be called with the IP-address of the connection as ReferenceID, which is proably not what you want.

@@ -897,7 +900,24 @@ fun(srp, Username :: string(), UserState :: term()) -> SslSocket = sslsocket() Reason = term() -

Opens an SSL connection to Host, Port.

+

Opens an SSL connection to Host, Port.

+ +

When the option verify is set to verify_peer the check + public_key:pkix_verify_hostname/2 + will be performed in addition to the usual x509-path validation checks. If the check fails the error {bad_cert, hostname_check_failed} will + be propagated to the path validation fun verify_fun, where it is possible to do customized + checks by using the full possibilitis of the public_key:pkix_verify_hostname/2 API. + + When the option server_name_indication is provided, its value (the DNS name) will be used as ReferenceID + to public_key:pkix_verify_hostname/2. + When no server_name_indication option is given, the Host argument will be used as + Server Name Indication extension. The Host argument will also be used for the + public_key:pkix_verify_hostname/2 check and if the Host + argument is an inet:ip_address() the ReferenceID used for the check will be {ip, Host} otherwise + dns_id will be assumed with a fallback to ip if that fails.

+

According to good practices certificates should not use IP-addresses as "server names". It would + be very surprising if this happen outside a closed network.

+
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 4e592c02ec..054e3b7ae3 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1003,7 +1003,7 @@ validate_option(server_name_indication = Opt, Value) when is_list(Value) -> validate_option(server_name_indication, undefined = Value) -> Value; validate_option(server_name_indication, disable) -> - undefined; + disable; validate_option(sni_hosts, []) -> []; diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 0dd5e5c5cf..a3333d35e9 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -138,13 +138,8 @@ validate(_, {bad_cert, _} = Reason, _) -> {fail, Reason}; validate(_, valid, UserState) -> {valid, UserState}; -validate(Cert, valid_peer, UserState = {client, _,_, Hostname, _, _}) when Hostname =/= undefined -> - case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}]) of - true -> - {valid, UserState}; - false -> - {fail, {bad_cert, hostname_check_failed}} - end; +validate(Cert, valid_peer, UserState = {client, _,_, Hostname, _, _}) when Hostname =/= disable -> + verify_hostname(Hostname, Cert, UserState); validate(_, valid_peer, UserState) -> {valid, UserState}. @@ -337,3 +332,32 @@ new_trusteded_chain(DerCert, [_ | Rest]) -> new_trusteded_chain(DerCert, Rest); new_trusteded_chain(_, []) -> unknown_ca. + +verify_hostname({fallback, Hostname}, Cert, UserState) when is_list(Hostname) -> + case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}]) of + true -> + {valid, UserState}; + false -> + case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}]) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end + end; + +verify_hostname({fallback, Hostname}, Cert, UserState) -> + case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}]) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end; + +verify_hostname(Hostname, Cert, UserState) -> + case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}]) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index c6f67eb696..099b5f8ffa 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -496,12 +496,13 @@ certify(internal, #certificate{}, certify(internal, #certificate{} = Cert, #state{negotiated_version = Version, role = Role, + host = Host, cert_db = CertDbHandle, cert_db_ref = CertDbRef, crl_db = CRLDbInfo, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, - Opts, CRLDbInfo, Role) of + Opts, CRLDbInfo, Role, Host) of {PeerCert, PublicKeyInfo} -> handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index b1661624b5..0ee9ee3322 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -50,7 +50,7 @@ finished/5, next_protocol/1]). %% Handle handshake messages --export([certify/6, client_certificate_verify/6, certificate_verify/6, verify_signature/5, +-export([certify/7, client_certificate_verify/6, certificate_verify/6, verify_signature/5, master_secret/4, server_key_exchange_hash/2, verify_connection/6, init_handshake_history/0, update_handshake_history/3, verify_server_key/5 ]). @@ -389,21 +389,21 @@ verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, %%-------------------------------------------------------------------- -spec certify(#certificate{}, db_handle(), certdb_ref(), #ssl_options{}, term(), - client | server) -> {der_cert(), public_key_info()} | #alert{}. + client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - Opts, CRLDbHandle, Role) -> + Opts, CRLDbHandle, Role, Host) -> + ServerName = server_name(Opts#ssl_options.server_name_indication, Host, Role), [PeerCert | _] = ASN1Certs, try {TrustedCert, CertPath} = ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, Opts#ssl_options.partial_chain), ValidationFunAndState = validation_fun_and_state(Opts#ssl_options.verify_fun, Role, - CertDbHandle, CertDbRef, - Opts#ssl_options.server_name_indication, + CertDbHandle, CertDbRef, ServerName, Opts#ssl_options.crl_check, CRLDbHandle, CertPath), case public_key:pkix_path_validation(TrustedCert, CertPath, @@ -1528,6 +1528,8 @@ select_shared_curve([Curve | Rest], Curves) -> sni(undefined) -> undefined; +sni(disable) -> + undefined; sni(Hostname) -> #sni{hostname = Hostname}. @@ -2353,3 +2355,9 @@ available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Su available_signature_algs(_, _, _, _) -> undefined. +server_name(_, _, server) -> + undefined; %% Not interesting to check your own name. +server_name(undefined, Host, client) -> + {fallback, Host}; %% Fallback to Host argument to connect +server_name(SNI, _, client) -> + SNI. %% If Server Name Indication is available diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 03676cb828..e080de95f6 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -25,6 +25,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/inet.hrl"). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -55,7 +57,10 @@ sni_tests() -> sni_no_match, no_sni_header_fun, sni_match_fun, - sni_no_match_fun]. + sni_no_match_fun, + dns_name, + ip_fallback, + no_ip_fallback]. init_per_suite(Config0) -> catch crypto:stop(), @@ -112,6 +117,65 @@ sni_no_match(Config) -> sni_no_match_fun(Config) -> run_sni_fun_handshake(Config, "c.server", undefined, "server Peer cert"). +dns_name(Config) -> + Hostname = "OTP.test.server", + #{server_config := ServerConf, + client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => + #{root => [], + intermediates => [[]], + peer => [{extensions, [#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = [{dNSName, Hostname}], + critical = false}]}]}, + client_chain => + #{root => [], + intermediates => [[]], + peer => []}}), + unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config), + successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, Hostname} | ClientConf], undefined, Config), + unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf], undefined, Config), + successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf], undefined, Config). + +ip_fallback(Config) -> + Hostname = net_adm:localhost(), + {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), + IPStr = tuple_to_list(IP), + #{server_config := ServerConf, + client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => + #{root => [], + intermediates => [[]], + peer => [{extensions, [#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = [{dNSName, Hostname}, + {iPAddress, IPStr}], + critical = false}]} + ]}, + client_chain => + #{root => [], + intermediates => [[]], + peer => []}}), + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). + +no_ip_fallback(Config) -> + Hostname = net_adm:localhost(), + {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), + #{server_config := ServerConf, + client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => + #{root => [], + intermediates => [[]], + peer => [{extensions, [#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = [{dNSName, Hostname}], + critical = false}]} + ]}, + client_chain => + #{root => [], + intermediates => [[]], + peer => []}}), + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), + unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). + %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ @@ -217,3 +281,37 @@ run_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN), ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +successfull_connect(ServerOptions, ClientOptions, Hostname0, Config) -> + {ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config), + Hostname = host_name(Hostname0, Hostname1), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOptions}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) -> + {ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config), + Hostname = host_name(Hostname0, Hostname1), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOptions}]), + + ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}, + Client, {error, {tls_alert, "handshake failure"}}). +host_name(undefined, Hostname) -> + Hostname; +host_name(Hostname, _) -> + Hostname. -- cgit v1.2.3 From 138e63eb7eb2248ff3821d6dda4a64b0479b480c Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 6 Oct 2017 17:26:43 +0200 Subject: ssl: Sessions must be registered with SNI if exists --- lib/ssl/src/ssl_connection.erl | 8 +++++- lib/ssl/test/ssl_sni_SUITE.erl | 62 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 099b5f8ffa..31e7738b4d 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -702,6 +702,7 @@ cipher(internal, #finished{verify_data = Data} = Finished, expecting_finished = true, session = #session{master_secret = MasterSecret} = Session0, + ssl_options = SslOpts, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, @@ -709,7 +710,7 @@ cipher(internal, #finished{verify_data = Data} = Finished, get_current_prf(ConnectionStates0, read), MasterSecret, Handshake0) of verified -> - Session = register_session(Role, Host, Port, Session0), + Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0), cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); #alert{} = Alert -> @@ -2097,6 +2098,11 @@ register_session(server, _, Port, #session{is_resumable = new} = Session0) -> register_session(_, _, _, Session) -> Session. %% Already registered +host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) -> + Hostname; +host_id(_, Host, _) -> + Host. + handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0, protocol_cb = Connection} = State0) -> diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index e080de95f6..13cb567110 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -60,7 +60,8 @@ sni_tests() -> sni_no_match_fun, dns_name, ip_fallback, - no_ip_fallback]. + no_ip_fallback, + dns_name_reuse]. init_per_suite(Config0) -> catch crypto:stop(), @@ -87,6 +88,13 @@ end_per_suite(_) -> ssl:stop(), application:stop(crypto). +init_per_testcase(TestCase, Config) when TestCase == ip_fallback; + TestCase == no_ip_fallback; + TestCase == dns_name_reuse -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + ct:timetrap({seconds, 20}), + Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), @@ -176,7 +184,57 @@ no_ip_fallback(Config) -> successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). - +dns_name_reuse(Config) -> + SNIHostname = "OTP.test.server", + #{server_config := ServerConf, + client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => + #{root => [], + intermediates => [[]], + peer => [{extensions, [#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = [{dNSName, SNIHostname}], + critical = false}]}]}, + client_chain => + #{root => [], + intermediates => [[]], + peer => []}}), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerConf}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, [{verify, verify_peer}, + {server_name_indication, SNIHostname} | ClientConf]}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(1000), + + Client1 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), + + ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}, + Client1, {error, {tls_alert, "handshake failure"}}), + ssl_test_lib:close(Client0). %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ %%-------------------------------------------------------------------- -- cgit v1.2.3 From d55f38f1d7804ffe6e9b7f7f7ee1a4d6690179ab Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 10 Oct 2017 18:54:52 +0200 Subject: public_key: Fix dialyzer spec --- lib/public_key/src/public_key.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index cc01b61433..c1ad72b585 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -850,10 +850,10 @@ pkix_crls_validate(OtpCert, DPAndCRLs0, Options) -> %-------------------------------------------------------------------- -spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(), - ReferenceIDs :: [{uri_id | dns_id | oid(), string()}]) -> boolean(). + ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | oid(), string()}]) -> boolean(). -spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(), - ReferenceIDs :: [{uri_id | dns_id | oid(), string()}], + ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | oid(), string()}], Options :: proplists:proplist()) -> boolean(). %% Description: Validates a hostname to RFC 6125 -- cgit v1.2.3 From 410d4b61d9fbd040b962c115f312f1e7080c5561 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 16 Oct 2017 11:17:53 +0200 Subject: ssl: Fix test cases to work on all test platforms Use hradcoded rsa keys as this will work on all legacy platforms. In test case dns_name_reuse only do the relevant client check in the final test. --- lib/ssl/test/ssl_sni_SUITE.erl | 73 ++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 13cb567110..7e78c41444 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -129,75 +129,80 @@ dns_name(Config) -> Hostname = "OTP.test.server", #{server_config := ServerConf, client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => - #{root => [], - intermediates => [[]], + #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], peer => [{extensions, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = [{dNSName, Hostname}], - critical = false}]}]}, + critical = false}]}, + {key, ssl_test_lib:hardcode_rsa_key(3)}]}, client_chain => - #{root => [], - intermediates => [[]], - peer => []}}), + #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}), unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config), successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, Hostname} | ClientConf], undefined, Config), unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf], undefined, Config), successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf], undefined, Config). - + ip_fallback(Config) -> Hostname = net_adm:localhost(), {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), IPStr = tuple_to_list(IP), #{server_config := ServerConf, client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => - #{root => [], - intermediates => [[]], + #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], peer => [{extensions, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = [{dNSName, Hostname}, {iPAddress, IPStr}], - critical = false}]} - ]}, + critical = false}]}, + {key, ssl_test_lib:hardcode_rsa_key(3)}]}, client_chain => - #{root => [], - intermediates => [[]], - peer => []}}), + #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}), successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). - + no_ip_fallback(Config) -> Hostname = net_adm:localhost(), {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), #{server_config := ServerConf, client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => - #{root => [], - intermediates => [[]], + #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], peer => [{extensions, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = [{dNSName, Hostname}], - critical = false}]} + critical = false}]}, + {key, ssl_test_lib:hardcode_rsa_key(3)} ]}, client_chain => - #{root => [], - intermediates => [[]], - peer => []}}), + #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}), successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). - + dns_name_reuse(Config) -> SNIHostname = "OTP.test.server", #{server_config := ServerConf, client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => - #{root => [], - intermediates => [[]], + #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], peer => [{extensions, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = [{dNSName, SNIHostname}], - critical = false}]}]}, + critical = false} + ]}, + {key, ssl_test_lib:hardcode_rsa_key(3)} + ]}, client_chain => - #{root => [], - intermediates => [[]], - peer => []}}), + #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -215,11 +220,10 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, [{verify, verify_peer}, {server_name_indication, SNIHostname} | ClientConf]}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, + receive + {Server, _} -> + ok + end, Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, @@ -232,8 +236,7 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}, - Client1, {error, {tls_alert, "handshake failure"}}), + ssl_test_lib:check_result(Client1, {error, {tls_alert, "handshake failure"}}), ssl_test_lib:close(Client0). %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ -- cgit v1.2.3 From 7d02ac57c576a1aa0331b25a473f085e1b04dfe0 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 28 Sep 2017 13:19:33 +0200 Subject: public_key: Added IP4 address checks to hostname_verification tests --- lib/public_key/test/public_key_SUITE.erl | 40 ++++++++++++++++++++++ .../pkix_verify_hostname_subjAltName_IP.pem | 13 +++++++ .../public_key_SUITE_data/verify_hostname_ip.conf | 17 +++++++++ 3 files changed, 70 insertions(+) create mode 100644 lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem create mode 100644 lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 374fb20375..6741a2e30c 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -47,6 +47,7 @@ all() -> pkix_iso_rsa_oid, pkix_iso_dsa_oid, pkix_crl, general_name, pkix_verify_hostname_cn, pkix_verify_hostname_subjAltName, + pkix_verify_hostname_subjAltName_IP, pkix_verify_hostname_options, pkix_test_data_all_default, pkix_test_data, @@ -984,6 +985,45 @@ pkix_verify_hostname_options(Config) -> [{fqdn_fun, fun(_) -> default end}]), false = public_key:pkix_verify_hostname(Cert, [{uri_id,"some://very.wrong.domain"}]). +%%-------------------------------------------------------------------- +%% To generate the PEM file contents: +%% +%% openssl req -x509 -nodes -newkey rsa:1024 -keyout /dev/null -extensions SAN -config public_key_SUITE_data/verify_hostname_ip.conf 2>/dev/null > public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem +%% +%% Subject: C=SE, CN=example.com +%% Subject Alternative Name: DNS:1.2.3.4, IP=5.6.7.8, URI:https://10.11.12.13 + +pkix_verify_hostname_subjAltName_IP(Config) -> + DataDir = proplists:get_value(data_dir, Config), + {ok,Bin} = file:read_file(filename:join(DataDir,"pkix_verify_hostname_subjAltName_IP.pem")), + Cert = public_key:pkix_decode_cert(element(2,hd(public_key:pem_decode(Bin))), otp), + + %% Print the tests that a matchfun has to handle + catch public_key:pkix_verify_hostname(Cert, [{some_tag,"some.domain"}, + {some_other_tag,[a,b,3,4]}], + [{match_fun, + fun(Ref,Pres) -> + ct:pal("~p:~p:~nRef : ~p~nPres: ~p",[?MODULE,?LINE,Ref,Pres]), + false + end}]), + + false = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://10.11.12.14"}]), + true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://10.11.12.13"}]), + true = public_key:pkix_verify_hostname(Cert, [{dns_id,"1.2.3.4"}]), + false = public_key:pkix_verify_hostname(Cert, [{dns_id,"5.6.7.8"}]), + true = public_key:pkix_verify_hostname(Cert, [{ip,[5,6,7,8]}], + [{match_fun, + fun({ip,IPref},{iPAddress,IPpres}) -> + ct:pal("~p:~p: IPref=~p, IPpres=~p",[?MODULE,?LINE,IPref,IPpres]), + IPref == IPpres; + (Ref,Pres) -> + ct:pal("~p:~p:~nRef : ~p~nPres: ~p",[?MODULE,?LINE,Ref,Pres]), + default + end}]). + + + + %%-------------------------------------------------------------------- pkix_iso_rsa_oid() -> [{doc, "Test workaround for supporting certs that use ISO oids" diff --git a/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem b/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem new file mode 100644 index 0000000000..e83dfdb646 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7TCCAVagAwIBAgIJAJftQyvUMLESMA0GCSqGSIb3DQEBCwUAMB8xCzAJBgNV +BAYTAlNFMRAwDgYDVQQDEwc1LjYuNy44MB4XDTE3MDkyODExMTY0MFoXDTE3MTAy +ODExMTY0MFowHzELMAkGA1UEBhMCU0UxEDAOBgNVBAMTBzUuNi43LjgwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAK1jLhGZcI0D5RzcUBjXuQ1636gaIlArdM4b +woLpJIh2Tk4pCbEv6hOX0KTl8qPs8lCMOIOvQmoZwj0Ia1B5By9xHybJ30pNIoLy +iqOdFwjlqTjhBVR1NjmcwgIRW1f/fqQ0pQznyIo/OwieNYlCwWzIUx1ZBLlwKAfZ +YWozJaLZAgMBAAGjMTAvMC0GA1UdEQQmMCSCBzEuMi4zLjSHBAUGBwiGE2h0dHBz +Oi8vMTAuMTEuMTIuMTMwDQYJKoZIhvcNAQELBQADgYEADapgA5dTBSrQMTAOGI7h +SFpaF1y9EHSnCxofugR2EJNyNyPELFBQ9etIgU8fw67D495OSfy/3X3ngNAFOuds +OENGq0JyBrO+HsgexslKF7LGc0oFlHfuZwyO7MLNFTG9dHG393dqduwGsEAuFh09 +SvMMJh3VTEHu6P+9O8YusdI= +-----END CERTIFICATE----- diff --git a/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf b/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf new file mode 100644 index 0000000000..2b905e2c4f --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf @@ -0,0 +1,17 @@ +[req] +prompt = no +distinguished_name = DN + +[DN] +C=SE +CN=example.com +CN=5.6.7.8 + +[SAN] +subjectAltName = @alt_names + +[alt_names] +DNS = 1.2.3.4 +IP = 5.6.7.8 +URI = https://10.11.12.13 + -- cgit v1.2.3 From 28e032d29013203bd32917ee495cc202f0bb6b4e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 28 Sep 2017 16:46:42 +0200 Subject: public_key: verify ip (both v4 and v6) --- lib/public_key/doc/src/public_key.xml | 14 +++++--- lib/public_key/src/public_key.erl | 37 ++++++++++++++++++++-- lib/public_key/test/public_key_SUITE.erl | 24 ++++++-------- .../pkix_verify_hostname_subjAltName_IP.pem | 22 ++++++------- .../public_key_SUITE_data/verify_hostname_ip.conf | 3 +- 5 files changed, 67 insertions(+), 33 deletions(-) diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index fcf37a7a4d..3040f2db0d 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -871,12 +871,13 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, Cert = der_encoded() | #'OTPCertificate'{} ReferenceIDs = [ RefID ] - RefID = {IdType,string()} - IdType = dns_id | srv_id | uri_id + RefID = {dns_id,string()} | {srv_id,string()} | {uri_id,string()} | {ip,inet:ip_address()|string()} | {OtherRefID,term()}} + OtherRefID = atom() Opts = [ PvhOpt() ] PvhOpt = [MatchOpt | FailCallBackOpt | FqdnExtractOpt] - MatchOpt = {fun(RefId | FQDN::string(), PresentedID) -> boolean() | default} - PresentedID = {dNSName,string()} | {uniformResourceIdentifier,string()} + MatchOpt = {match_fun, fun(RefId | FQDN::string(), PresentedID) -> boolean() | default} + PresentedID = {dNSName,string()} | {uniformResourceIdentifier,string() | {iPAddress,list(byte())} | {OtherPresId,term()}} + OtherPresID = atom() FailCallBackOpt = {fail_callback, fun(#'OTPCertificate'{}) -> boolean()} FqdnExtractOpt = {fqdn_fun, fun(RefID) -> FQDN::string() | default | undefined} @@ -893,6 +894,11 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, code examples describes this function more detailed.

+

The {OtherRefId,term()} is defined by the user and is passed to the match_fun, if defined. + If that term is a binary, it will be converted to a string. +

+

The ip takes a 4-tuple or a +

diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index cc01b61433..c3f2d791a3 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -1454,13 +1454,43 @@ verify_hostname_match_default0({dns_id,R}, {dNSName,P}) -> R==P; verify_hostname_match_default0({uri_id,R}, {uniformResourceIdentifier,P}) -> R==P; -verify_hostname_match_default0({srv_id,R}, {T,P}) when T == srvName ; - T == ?srvName_OID -> +verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 4 -> + %% IPv4 + try + list_to_tuple(P) + == if is_tuple(R), size(R)==4 -> R; + is_list(R) -> ok(inet:parse_ipv4strict_address(R)) + end + catch + _:_ -> + false + end; + +verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 16 -> + %% IPv6. The length 16 is due to the certificate specification. + try + l16_to_tup(P) + == if is_tuple(R), size(R)==8 -> R; + is_list(R) -> ok(inet:parse_ipv6strict_address(R)) + end + catch + _:_ -> + false + end; +verify_hostname_match_default0({srv_id,R}, {srvName,P}) -> + R==P; +verify_hostname_match_default0({srv_id,R}, {?srvName_OID,P}) -> R==P; verify_hostname_match_default0(_, _) -> false. +ok({ok,X}) -> X. +l16_to_tup(L) -> list_to_tuple(l16_to_tup(L, [])). +%% +l16_to_tup([A,B|T], Acc) -> l16_to_tup(T, [(A bsl 8) bor B | Acc]); +l16_to_tup([], Acc) -> lists:reverse(Acc). + match_wild(A, [$*|B]) -> match_wild_suffixes(A, B); match_wild([C|A], [ C|B]) -> match_wild(A, B); match_wild([], []) -> true; @@ -1505,7 +1535,8 @@ to_lower_ascii(C) when $A =< C,C =< $Z -> C + ($a-$A); to_lower_ascii(C) -> C. to_string(S) when is_list(S) -> S; -to_string(B) when is_binary(B) -> binary_to_list(B). +to_string(B) when is_binary(B) -> binary_to_list(B); +to_string(X) -> X. format_details([]) -> no_relevant_crls; diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 6741a2e30c..0077c7908c 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -991,7 +991,7 @@ pkix_verify_hostname_options(Config) -> %% openssl req -x509 -nodes -newkey rsa:1024 -keyout /dev/null -extensions SAN -config public_key_SUITE_data/verify_hostname_ip.conf 2>/dev/null > public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem %% %% Subject: C=SE, CN=example.com -%% Subject Alternative Name: DNS:1.2.3.4, IP=5.6.7.8, URI:https://10.11.12.13 +%% Subject Alternative Name: DNS:1.2.3.4, DNS: abcd:ef::1, IP:5.6.7.8, URI:https://10.11.12.13 pkix_verify_hostname_subjAltName_IP(Config) -> DataDir = proplists:get_value(data_dir, Config), @@ -1000,28 +1000,24 @@ pkix_verify_hostname_subjAltName_IP(Config) -> %% Print the tests that a matchfun has to handle catch public_key:pkix_verify_hostname(Cert, [{some_tag,"some.domain"}, - {some_other_tag,[a,b,3,4]}], + {ip, {5,6,7,8}} + ], [{match_fun, fun(Ref,Pres) -> ct:pal("~p:~p:~nRef : ~p~nPres: ~p",[?MODULE,?LINE,Ref,Pres]), false end}]), - false = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://10.11.12.14"}]), + false = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://1.2.3.4"}]), true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://10.11.12.13"}]), true = public_key:pkix_verify_hostname(Cert, [{dns_id,"1.2.3.4"}]), + true = public_key:pkix_verify_hostname(Cert, [{dns_id,<<"1.2.3.4">>}]), false = public_key:pkix_verify_hostname(Cert, [{dns_id,"5.6.7.8"}]), - true = public_key:pkix_verify_hostname(Cert, [{ip,[5,6,7,8]}], - [{match_fun, - fun({ip,IPref},{iPAddress,IPpres}) -> - ct:pal("~p:~p: IPref=~p, IPpres=~p",[?MODULE,?LINE,IPref,IPpres]), - IPref == IPpres; - (Ref,Pres) -> - ct:pal("~p:~p:~nRef : ~p~nPres: ~p",[?MODULE,?LINE,Ref,Pres]), - default - end}]). - - + true = public_key:pkix_verify_hostname(Cert, [{ip, "aBcD:ef:0::0:1"}]), + true = public_key:pkix_verify_hostname(Cert, [{ip, {16#abcd,16#ef,0,0,0,0,0,1}}]), + true = public_key:pkix_verify_hostname(Cert, [{ip, "5.6.7.8"}]), + true = public_key:pkix_verify_hostname(Cert, [{ip, <<"5.6.7.8">>}]), + true = public_key:pkix_verify_hostname(Cert, [{ip, {5,6,7,8}}]). %%-------------------------------------------------------------------- diff --git a/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem b/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem index e83dfdb646..f9ffb257b5 100644 --- a/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem +++ b/lib/public_key/test/public_key_SUITE_data/pkix_verify_hostname_subjAltName_IP.pem @@ -1,13 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB7TCCAVagAwIBAgIJAJftQyvUMLESMA0GCSqGSIb3DQEBCwUAMB8xCzAJBgNV -BAYTAlNFMRAwDgYDVQQDEwc1LjYuNy44MB4XDTE3MDkyODExMTY0MFoXDTE3MTAy -ODExMTY0MFowHzELMAkGA1UEBhMCU0UxEDAOBgNVBAMTBzUuNi43LjgwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAK1jLhGZcI0D5RzcUBjXuQ1636gaIlArdM4b -woLpJIh2Tk4pCbEv6hOX0KTl8qPs8lCMOIOvQmoZwj0Ia1B5By9xHybJ30pNIoLy -iqOdFwjlqTjhBVR1NjmcwgIRW1f/fqQ0pQznyIo/OwieNYlCwWzIUx1ZBLlwKAfZ -YWozJaLZAgMBAAGjMTAvMC0GA1UdEQQmMCSCBzEuMi4zLjSHBAUGBwiGE2h0dHBz -Oi8vMTAuMTEuMTIuMTMwDQYJKoZIhvcNAQELBQADgYEADapgA5dTBSrQMTAOGI7h -SFpaF1y9EHSnCxofugR2EJNyNyPELFBQ9etIgU8fw67D495OSfy/3X3ngNAFOuds -OENGq0JyBrO+HsgexslKF7LGc0oFlHfuZwyO7MLNFTG9dHG393dqduwGsEAuFh09 -SvMMJh3VTEHu6P+9O8YusdI= +MIIB/zCCAWigAwIBAgIJAMoSejmTjwAGMA0GCSqGSIb3DQEBCwUAMB8xCzAJBgNV +BAYTAlNFMRAwDgYDVQQDEwc1LjYuNy44MB4XDTE3MDkyODE0MDAxNVoXDTE3MTAy +ODE0MDAxNVowHzELMAkGA1UEBhMCU0UxEDAOBgNVBAMTBzUuNi43LjgwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAMUPU89KwVbTCDkyxQSz3wprMbZTLe35K6jm +Q7oY1rJyVXjsFHwZrFqqNMScEyX40rJhczQ2Z9etEX6qYLbdb/DZeFcKo14fR583 +QMFZC+qqpLWHdvjaQN0KwD99VFeZIGpRgywG8SR+BXZjDHUkGsMrikAEJtf0Tgih +IPyiFtiJAgMBAAGjQzBBMD8GA1UdEQQ4MDaCBzEuMi4zLjSHBAUGBwiHEKvNAO8A +AAAAAAAAAAAAAAGGE2h0dHBzOi8vMTAuMTEuMTIuMTMwDQYJKoZIhvcNAQELBQAD +gYEAtWVeQaRFZ0kH/pzSWMSsOCUrjbwlWRwDNbagNKoM6nCRv0QQ59fG6XrVZwR3 +c0s5arlMh3U2+bjKE+Iq9+b/lN1lGzf8iaAqBNa7KptwTSUEY3TiNG5X0zlSXKTI +3z7AaUEtghL9ImCPj5V3tVksqWd7U0zLmeeLZnM+wGAL9Hc= -----END CERTIFICATE----- diff --git a/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf b/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf index 2b905e2c4f..0a738f2586 100644 --- a/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf +++ b/lib/public_key/test/public_key_SUITE_data/verify_hostname_ip.conf @@ -12,6 +12,7 @@ subjectAltName = @alt_names [alt_names] DNS = 1.2.3.4 -IP = 5.6.7.8 +IP.1 = 5.6.7.8 +IP.2 = abcd:ef::1 URI = https://10.11.12.13 -- cgit v1.2.3 From 92cfb8ee136eea2ace96c3cee85f758a670b2bf7 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Wed, 22 Nov 2017 12:23:43 +0100 Subject: Update version numbers --- lib/public_key/vsn.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index bb96c2237d..c01d8820f2 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 1.5 +PUBLIC_KEY_VSN = 1.5.1 -- cgit v1.2.3 From 31a1cd146bf6d0caf1d3fe8005b7e6307710205d Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Wed, 22 Nov 2017 12:23:57 +0100 Subject: Update release notes --- lib/public_key/doc/src/notes.xml | 24 +++++++++++++ lib/ssl/doc/src/notes.xml | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index 7a7c828760..a4c0194328 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -35,6 +35,30 @@ notes.xml +
Public_Key 1.5.1 + +
Improvements and New Features + + +

+ Hostname verification: Add handling of the general name + iPAddress in certificate's subject alternative + name extension (subjAltName).

+

+ Own Id: OTP-14653

+
+ +

+ Correct key handling in pkix_test_data/1 and use a + generic example mail address instead of an existing one.

+

+ Own Id: OTP-14766

+
+
+
+ +
+
Public_Key 1.5
Fixed Bugs and Malfunctions diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 4c6a204e63..a8450c2630 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -28,6 +28,84 @@

This document describes the changes made to the SSL application.

+
SSL 8.2.2 + +
Fixed Bugs and Malfunctions + + +

+ TLS sessions must be registered with SNI if provided, so + that sessions where client hostname verification would + fail can not connect reusing a session created when the + server name verification succeeded.

+

+ Own Id: OTP-14632

+
+ +

An erlang TLS server configured with cipher suites + using rsa key exchange, may be vulnerable to an Adaptive + Chosen Ciphertext attack (AKA Bleichenbacher attack) + against RSA, which when exploited, may result in + plaintext recovery of encrypted messages and/or a + Man-in-the-middle (MiTM) attack, despite the attacker not + having gained access to the server’s private key + itself. CVE-2017-1000385 +

Exploiting this vulnerability to perform + plaintext recovery of encrypted messages will, in most + practical cases, allow an attacker to read the plaintext + only after the session has completed. Only TLS sessions + established using RSA key exchange are vulnerable to this + attack.

Exploiting this vulnerability to conduct + a MiTM attack requires the attacker to complete the + initial attack, which may require thousands of server + requests, during the handshake phase of the targeted + session within the window of the configured handshake + timeout. This attack may be conducted against any TLS + session using RSA signatures, but only if cipher suites + using RSA key exchange are also enabled on the server. + The limited window of opportunity, limitations in + bandwidth, and latency make this attack significantly + more difficult to execute.

RSA key exchange is + enabled by default although least prioritized if server + order is honored. For such a cipher suite to be chosen it + must also be supported by the client and probably the + only shared cipher suite.

Captured TLS sessions + encrypted with ephemeral cipher suites (DHE or ECDHE) are + not at risk for subsequent decryption due to this + vulnerability.

As a workaround if default cipher + suite configuration was used you can configure the server + to not use vulnerable suites with the ciphers option like + this:

{ciphers, [Suite || Suite <- + ssl:cipher_suites(), element(1,Suite) =/= rsa]}

+ that is your code will look somethingh like this:

+ ssl:listen(Port, [{ciphers, [Suite || Suite <- + ssl:cipher_suites(), element(1,S) =/= rsa]} | Options]). +

Thanks to Hanno Böck, Juraj Somorovsky and + Craig Young for reporting this vulnerability.

+

+ Own Id: OTP-14748

+
+
+
+ + +
Improvements and New Features + + +

+ If no SNI is available and the hostname is an IP-address + also check for IP-address match. This check is not as + good as a DNS hostname check and certificates using + IP-address are not recommended.

+

+ Own Id: OTP-14655

+
+
+
+ +
+
SSL 8.2.1
Fixed Bugs and Malfunctions -- cgit v1.2.3 From 2302ea8ca97b8a9075e9234d15430c47d3a115c8 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Wed, 22 Nov 2017 12:23:59 +0100 Subject: Updated OTP version --- OTP_VERSION | 2 +- otp_versions.table | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/OTP_VERSION b/OTP_VERSION index b85240c18b..bf8f7ca9a2 100644 --- a/OTP_VERSION +++ b/OTP_VERSION @@ -1 +1 @@ -20.1.6 +20.1.7 diff --git a/otp_versions.table b/otp_versions.table index 4c6bb0c53d..3d841f2d6c 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -1,3 +1,4 @@ +OTP-20.1.7 : public_key-1.5.1 ssl-8.2.2 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 erts-9.1.5 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 inets-6.4.4 jinterface-1.8 kernel-5.4 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssh-4.6.2 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 : OTP-20.1.6 : erts-9.1.5 ssh-4.6.2 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 inets-6.4.4 jinterface-1.8 kernel-5.4 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.5 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssl-8.2.1 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 : OTP-20.1.5 : erts-9.1.4 inets-6.4.4 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 jinterface-1.8 kernel-5.4 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.5 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssh-4.6.1 ssl-8.2.1 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 : OTP-20.1.4 : inets-6.4.3 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 erts-9.1.3 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 jinterface-1.8 kernel-5.4 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.5 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssh-4.6.1 ssl-8.2.1 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 : -- cgit v1.2.3