diff options
Diffstat (limited to 'lib/ssl')
37 files changed, 2149 insertions, 1222 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index d13ad09470..5a39cac9bc 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1999</year><year>2016</year> + <year>1999</year><year>2017</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -28,6 +28,122 @@ <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 8.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + ECDH-ECDSA key exchange supported, was accidently + dismissed in earlier versions.</p> + <p> + Own Id: OTP-14421</p> + </item> + <item> + <p> + Correct close semantics for active once connections. This + was a timing dependent bug the resulted in the close + message not always reaching the ssl user process.</p> + <p> + Own Id: OTP-14443</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + TLS-1.2 clients will now always send hello messages on + its own format, as opposed to earlier versions that will + send the hello on the lowest supported version, this is a + change supported by the latest RFC.</p> + <p> + This will make interoperability with some newer servers + smoother. Potentially, but unlikely, this could cause a + problem with older servers if they do not adhere to the + RFC and ignore unknown extensions.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-13820</p> + </item> + <item> + <p> + Allow Erlang/OTP to use OpenSSL in FIPS-140 mode, in + order to satisfy specific security requirements (mostly + by different parts of the US federal government). </p> + <p> + See the new crypto users guide "FIPS mode" chapter about + building and using the FIPS support which is disabled by + default.</p> + <p> + (Thanks to dszoboszlay and legoscia)</p> + <p> + Own Id: OTP-13921 Aux Id: PR-1180 </p> + </item> + <item> + <p> + Implemented DTLS cookie generation, required by spec, + instead of using a hardcoded value.</p> + <p> + Own Id: OTP-14076</p> + </item> + <item> + <p> + Implement sliding window replay protection of DTLS + records.</p> + <p> + Own Id: OTP-14077</p> + </item> + <item> + <p> + TLS client processes will by default call + public_key:pkix_verify_hostname/2 to verify the hostname + of the connection with the server certificates specified + hostname during certificate path validation. The user may + explicitly disables it. Also if the hostname can not be + derived from the first argument to connect or is not + supplied by the server name indication option, the check + will not be performed.</p> + <p> + Own Id: OTP-14197</p> + </item> + <item> + <p> + Extend connection_information/[1,2] . The values + session_id, master_secret, client_random and + server_random can no be accessed by + connection_information/2. Note only session_id will be + added to connection_information/1. The rational is that + values concerning the connection security should have to + be explicitly requested.</p> + <p> + Own Id: OTP-14291</p> + </item> + <item> + <p> + Chacha cipher suites are currently not tested enough to + be most preferred ones</p> + <p> + Own Id: OTP-14382</p> + </item> + <item> + <p> + Basic support for DTLS that been tested together with + OpenSSL.</p> + <p> + Test by providing the option {protocol, dtls} to the ssl + API functions connect and listen.</p> + <p> + Own Id: OTP-14388</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 8.1.3</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -197,7 +313,14 @@ message.</p> <p> Own Id: OTP-13853</p> + </item> + + <item> + <p>Correct handling of signature algorithm selection</p> + <p> + Own Id: OTP-13711</p> </item> + </list> </section> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 916b41742e..ca2dcbb761 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1999</year><year>2016</year> + <year>1999</year><year>2017</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -127,7 +127,7 @@ <item><p><c>hostname() | ipaddress()</c></p></item> <tag><c>hostname() =</c></tag> - <item><p><c>string()</c></p></item> + <item><p><c>string() - DNS hostname</c></p></item> <tag><c>ip_address() =</c></tag> <item><p><c>{N1,N2,N3,N4} % IPv4 | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 @@ -189,6 +189,11 @@ <taglist> + <tag><c>{protocol, tls | dtls}</c></tag> + <item><p>Choose TLS or DTLS protocol for the transport layer security. + Defaults to <c>tls</c> Introduced in OTP 20, DTLS support is considered + experimental in this release. DTLS over other transports than UDP are not yet supported.</p></item> + <tag><c>{cert, public_key:der_encoded()}</c></tag> <item><p>The DER-encoded users certificate. If this option is supplied, it overrides option <c>certfile</c>.</p></item> @@ -249,7 +254,7 @@ be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA, ROOT-CA, and so on. The default value is 1.</p></item> - <tag><c>{verify_fun, {Verifyfun :: fun(), InitialUserState :: + <tag><marker id="verify_fun"/><c>{verify_fun, {Verifyfun :: fun(), InitialUserState :: term()}}</c></tag> <item><p>The verification fun is to be defined as follows:</p> @@ -582,15 +587,23 @@ fun(srp, Username :: string(), UserState :: term()) -> <item><p>Specifies the username and password to use to authenticate to the server.</p></item> - <tag><c>{server_name_indication, hostname()}</c></tag> - <item><p>Can be specified when upgrading a TCP socket to a TLS - socket to use the TLS Server Name Indication extension.</p></item> + <tag><c>{server_name_indication, HostName :: hostname()}</c></tag> + <item><p>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 <seealso marker="ssl#connect-3">ssl:connect/3</seealso>. + Will also cause the client to preform host name verification of the peer certificate + <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname(PeerCert, [{dns_id, HostName}])</seealso> + </p> during the x509-path validation. If the check fails the error {bad_cert, hostname_check_failiure} will be + propagated to the path validation fun <seealso marker="#verify_fun">verify_fun</seealso> + </item> <tag><c>{server_name_indication, disable}</c></tag> <item> <p>When starting a TLS connection without upgrade, the Server Name - Indication extension is sent if possible. This option can be - used to disable that behavior.</p> + Indication extension is sent if possible that is can be derived from the Host argument + to <seealso marker="ssl#connect-3">ssl:connect/3</seealso>. + This option can be used to disable that behavior.</p> + <note><p> Note that this also disables the default host name verification check of the peer certificate.</p></note> </item> <tag><c>{fallback, boolean()}</c></tag> <item> @@ -935,13 +948,14 @@ fun(srp, Username :: string(), UserState :: term()) -> <fsummary>Returns all the connection information. </fsummary> <type> - <v>Item = protocol | cipher_suite | sni_hostname | ecc | atom()</v> + <v>Item = protocol | cipher_suite | sni_hostname | ecc | session_id | atom()</v> <d>Meaningful atoms, not specified above, are the ssl option names.</d> <v>Result = [{Item::atom(), Value::term()}]</v> <v>Reason = term()</v> </type> - <desc><p>Returns all relevant information about the connection, ssl options that - are undefined will be filtered out.</p> + <desc><p>Returns the most relevant information about the connection, ssl options that + are undefined will be filtered out. Note that values that affect the security of the + connection will only be returned if explicitly requested by connection_information/2.</p> </desc> </func> @@ -952,8 +966,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </fsummary> <type> <v>Items = [Item]</v> - <v>Item = protocol | cipher_suite | sni_hostname | atom()</v> - <d>Meaningful atoms, not specified above, are the ssl option names.</d> + <v>Item = protocol | cipher_suite | sni_hostname | ecc | session_id | client_random + | server_random | master_secret | atom()</v> + <d>Note that client_random, server_random and master_secret are values + that affect the security of connection. Meaningful atoms, not specified above, are the ssl option names.</d> <v>Result = [{Item::atom(), Value::term()}]</v> <v>Reason = term()</v> </type> diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 745db788e7..b6aafc3fa4 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -48,12 +48,12 @@ select_sni_extension/1]). %% Alert and close handling --export([encode_alert/3,send_alert/2, close/5]). +-export([encode_alert/3,send_alert/2, close/5, protocol_name/0]). %% Data handling -export([encode_data/3, passive_receive/2, next_record_if_active/1, handle_common_event/4, - send/3, socket/5]). + send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -115,7 +115,7 @@ send_handshake_flight(#state{socket = Socket, {Encoded, ConnectionStates} = encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0), send(Transport, Socket, Encoded), - start_flight(State0#state{connection_states = ConnectionStates}); + {State0#state{connection_states = ConnectionStates}, []}; send_handshake_flight(#state{socket = Socket, transport_cb = Transport, @@ -129,7 +129,7 @@ send_handshake_flight(#state{socket = Socket, {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), send(Transport, Socket, [HsBefore, EncChangeCipher]), - start_flight(State0#state{connection_states = ConnectionStates}); + {State0#state{connection_states = ConnectionStates}, []}; send_handshake_flight(#state{socket = Socket, transport_cb = Transport, @@ -145,7 +145,7 @@ send_handshake_flight(#state{socket = Socket, {HsAfter, ConnectionStates} = encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2), send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), - start_flight(State0#state{connection_states = ConnectionStates}); + {State0#state{connection_states = ConnectionStates}, []}; send_handshake_flight(#state{socket = Socket, transport_cb = Transport, @@ -159,7 +159,7 @@ send_handshake_flight(#state{socket = Socket, {HsAfter, ConnectionStates} = encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1), send(Transport, Socket, [EncChangeCipher, HsAfter]), - start_flight(State0#state{connection_states = ConnectionStates}). + {State0#state{connection_states = ConnectionStates}, []}. queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, connection_states = ConnectionStates0} = State) -> @@ -203,6 +203,14 @@ select_sni_extension(_) -> socket(Pid, Transport, Socket, Connection, _) -> dtls_socket:socket(Pid, Transport, Socket, Connection). +setopts(Transport, Socket, Other) -> + dtls_socket:setopts(Transport, Socket, Other). +getopts(Transport, Socket, Tag) -> + dtls_socket:getopts(Transport, Socket, Tag). + +protocol_name() -> + "DTLS". + %%==================================================================== %% tls_connection_sup API %%==================================================================== @@ -230,12 +238,14 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> end. callback_mode() -> - state_functions. + [state_functions, state_enter]. %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- +init(enter, _, State) -> + {keep_state, State}; init({call, From}, {start, Timeout}, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, @@ -250,7 +260,7 @@ init({call, From}, {start, Timeout}, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, - HelloVersion = dtls_record:lowest_protocol_version(SslOpts#ssl_options.versions), + 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 @@ -263,15 +273,22 @@ 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) -> - ssl_connection:init(Type, Event, - State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}}, - ?MODULE); + 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 => <<>>}}, + ?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); init(Type, Event, State) -> ssl_connection:init(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) -> @@ -285,29 +302,46 @@ error(_, _, _) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +hello(enter, _, #state{role = server} = State) -> + {keep_state, State}; +hello(enter, _, #state{role = client} = State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; hello(internal, #client_hello{cookie = <<>>, client_version = Version} = Hello, #state{role = server, transport_cb = Transport, - socket = Socket} = State0) -> - %% TODO: not hard code key + socket = Socket, + protocol_specific = #{current_cookie_secret := Secret}} = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), - Cookie = dtls_handshake:cookie(<<"secret">>, IP, Port, Hello), - VerifyRequest = dtls_handshake:hello_verify_request(Cookie, Version), + Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), + %% FROM RFC 6347 regarding HelloVerifyRequest message: + %% The server_version field has the same syntax as in TLS. However, in + %% order to avoid the requirement to do version negotiation in the + %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS + %% version 1.0 regardless of the version of TLS that is expected to be + %% 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(hello, 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} = State0) -> + socket = Socket, + protocol_specific = #{current_cookie_secret := Secret, + previous_cookie_secret := PSecret}} = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), - %% TODO: not hard code key - case dtls_handshake:cookie(<<"secret">>, IP, Port, Hello) of + case dtls_handshake:cookie(Secret, IP, Port, Hello) of Cookie -> handle_client_hello(Hello, State0); _ -> - %% Handle bad cookie as new cookie request RFC 6347 4.1.2 - hello(internal, Hello#client_hello{cookie = <<>>}, State0) + case dtls_handshake:cookie(PSecret, IP, Port, Hello) of + Cookie -> + handle_client_hello(Hello, State0); + _ -> + %% Handle bad cookie as new cookie request RFC 6347 4.1.2 + hello(internal, Hello#client_hello{cookie = <<>>}, State0) + end end; hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, host = Host, port = Port, @@ -358,6 +392,9 @@ hello(state_timeout, Event, State) -> hello(Type, Event, State) -> ssl_connection:hello(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); abbreviated(internal = Type, @@ -375,6 +412,9 @@ abbreviated(state_timeout, Event, State) -> abbreviated(Type, Event, State) -> ssl_connection:abbreviated(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); certify(internal = Type, #server_hello_done{} = Event, State) -> @@ -384,6 +424,9 @@ certify(state_timeout, Event, State) -> certify(Type, Event, State) -> ssl_connection:certify(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); cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, @@ -401,6 +444,8 @@ cipher(state_timeout, Event, State) -> cipher(Type, Event, State) -> ssl_connection:cipher(Type, Event, State, ?MODULE). +connection(enter, _, State) -> + {keep_state, State}; connection(info, Event, State) -> handle_info(Event, connection, State); connection(internal, #hello_request{}, #state{host = Host, port = Port, @@ -433,6 +478,9 @@ connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = connection(Type, Event, State) -> ssl_connection:connection(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). @@ -453,27 +501,46 @@ handle_info({Protocol, _, _, _, Data}, StateName, {stop, {shutdown, own_alert}} end; handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, close_tag = CloseTag, + #state{socket = Socket, + socket_options = #socket_options{active = Active}, + protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, + close_tag = CloseTag, negotiated_version = Version} = State) -> %% Note that as of DTLS 1.2 (TLS 1.1), %% failure to properly close a connection no longer requires that a %% session not be resumed. This is a change from DTLS 1.0 to conform %% with widespread implementation practice. - case Version of - {254, N} when N =< 253 -> - ok; - _ -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %%invalidate_session(Role, Host, Port, Session) - ok - end, - ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, {shutdown, transport_closed}}; + case (Active == false) andalso (CTs =/= []) of + false -> + case Version of + {254, N} when N =< 253 -> + ok; + _ -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %%invalidate_session(Role, Host, Port, Session) + ok + end, + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown, transport_closed}}; + true -> + %% Fixes non-delivery of final DTLS 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) + end; + +handle_info(new_cookie_secret, StateName, + #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + {next_state, StateName, State#state{protocol_specific = + CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => Secret}}}; handle_info(Msg, StateName, State) -> ssl_connection:handle_info(Msg, StateName, State). + handle_call(Event, From, StateName, State) -> ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). @@ -658,17 +725,36 @@ next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> {no_record, State#state{unprocessed_handshake_events = N-1}}; next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [CT | Rest]} + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} = Buffers, - connection_states = ConnStates0} = State) -> - case dtls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} + connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> + CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), + case dtls_record:replay_detect(CT, CurrentRead) of + false -> + decode_cipher_text(State#state{connection_states = ConnectionStates}) ; + true -> + %% Ignore replayed record + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}) end; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) + when Epoch > CurrentEpoch -> + %% TODO Buffer later Epoch message, drop it for now + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} + = Buffers, + connection_states = ConnectionStates} = State) -> + %% Drop old epoch message + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); next_record(#state{role = server, socket = {Listener, {Client, _}}, transport_cb = gen_udp} = State) -> @@ -713,31 +799,73 @@ next_event(connection = StateName, no_record, {#ssl_tls{epoch = Epoch, type = ?HANDSHAKE, version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State1, Epoch), - {next_state, StateName, State, Actions ++ MoreActions}; + {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + {NextRecord, State} = next_record(State2), + next_event(StateName, NextRecord, 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); {#ssl_tls{epoch = _Epoch, - version = _Version}, State} -> + version = _Version}, State1} -> %% TODO maybe buffer later epoch - {next_state, StateName, State, Actions}; + {Record, State} = next_record(State1), + next_event(StateName, Record, State, Actions); {#alert{} = Alert, State} -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; +next_event(connection = StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch} -> + {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; + #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); + %% 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); + _ -> + next_event(StateName, no_record, State0, Actions) + end; next_event(StateName, Record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State, Actions) -> + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> case Record of no_record -> - {next_state, StateName, State, Actions}; + {next_state, StateName, State0, Actions}; #ssl_tls{epoch = CurrentEpoch, version = Version} = Record -> {next_state, StateName, - dtls_version(StateName, Version, State), + dtls_version(StateName, Version, State0), [{next_event, internal, {protocol_record, Record}} | Actions]}; #ssl_tls{epoch = _Epoch, version = _Version} = _Record -> %% TODO maybe buffer later epoch - {next_state, StateName, State, Actions}; + {Record, State} = next_record(State0), + next_event(StateName, Record, State, Actions); #alert{} = Alert -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]} + end. + +decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers, + connection_states = ConnStates0} = State) -> + case dtls_record:decode_cipher_text(CT, ConnStates0) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnStates}}; + #alert{} = Alert -> + {Alert, State} end. dtls_version(hello, Version, #state{role = server} = State) -> @@ -766,13 +894,13 @@ next_flight(Flight) -> change_cipher_spec => undefined, handshakes_after_change_cipher_spec => []}. -start_flight(#state{transport_cb = gen_udp, - flight_state = {retransmit, Timeout}} = State) -> +handle_flight_timer(#state{transport_cb = gen_udp, + flight_state = {retransmit, Timeout}} = State) -> start_retransmision_timer(Timeout, State); -start_flight(#state{transport_cb = gen_udp, +handle_flight_timer(#state{transport_cb = gen_udp, flight_state = connection} = State) -> {State, []}; -start_flight(State) -> +handle_flight_timer(State) -> %% No retransmision needed i.e DTLS over SCTP {State#state{flight_state = reliable}, []}. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index a94954d8f2..37a46b862e 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -65,9 +65,8 @@ client_hello(Host, Port, Cookie, ConnectionStates, TLSVersion = dtls_v1:corresponding_tls_version(Version), CipherSuites = ssl_handshake:available_suites(UserSuites, TLSVersion), - Extensions = ssl_handshake:client_hello_extensions(Host, TLSVersion, CipherSuites, - SslOpts, ConnectionStates, Renegotiation), - + Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites, + SslOpts, ConnectionStates, Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, @@ -252,7 +251,7 @@ enc_handshake(#server_hello{} = HandshakeMsg, Version) -> {Type, <<?BYTE(DTLSMajor), ?BYTE(DTLSMinor), Rest/binary>>}; enc_handshake(HandshakeMsg, Version) -> - ssl_handshake:encode_handshake(HandshakeMsg, Version). + ssl_handshake:encode_handshake(HandshakeMsg, dtls_v1:corresponding_tls_version(Version)). bin_fragments(Bin, Size) -> bin_fragments(Bin, size(Bin), Size, 0, []). @@ -455,7 +454,7 @@ merge_fragments(#handshake_fragment{ fragment_offset = PreviousOffSet, fragment_length = CurrentLen}) when CurrentLen < PreviousLen -> Previous; -%% Next fragment +%% Next fragment, might be overlapping merge_fragments(#handshake_fragment{ fragment_offset = PreviousOffSet, fragment_length = PreviousLen, @@ -464,10 +463,26 @@ merge_fragments(#handshake_fragment{ #handshake_fragment{ fragment_offset = CurrentOffSet, fragment_length = CurrentLen, - fragment = CurrentData}) when PreviousOffSet + PreviousLen == CurrentOffSet-> - Previous#handshake_fragment{ - fragment_length = PreviousLen + CurrentLen, - fragment = <<PreviousData/binary, CurrentData/binary>>}; + fragment = CurrentData}) + when PreviousOffSet + PreviousLen >= CurrentOffSet andalso + PreviousOffSet + PreviousLen < CurrentOffSet + CurrentLen -> + CurrentStart = PreviousOffSet + PreviousLen - CurrentOffSet, + <<_:CurrentStart/bytes, Data/binary>> = CurrentData, + Previous#handshake_fragment{ + fragment_length = PreviousLen + CurrentLen - CurrentStart, + fragment = <<PreviousData/binary, Data/binary>>}; +%% already fully contained fragment +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen + } = Previous, + #handshake_fragment{ + fragment_offset = CurrentOffSet, + fragment_length = CurrentLen}) + when PreviousOffSet + PreviousLen >= CurrentOffSet andalso + PreviousOffSet + PreviousLen >= CurrentOffSet + CurrentLen -> + Previous; + %% No merge there is a gap merge_fragments(Previous, Current) -> [Previous, Current]. diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index 0a980c5f31..50e92027d2 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ -include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes -define(HELLO_VERIFY_REQUEST, 3). +-define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}). -record(client_hello, { client_version, diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index f447897d59..8a7f8c1d0a 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -44,9 +44,9 @@ -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/2]). + is_acceptable_version/2, hello_version/2]). --export([save_current_connection_state/2, next_epoch/2]). +-export([save_current_connection_state/2, next_epoch/2, get_connection_state_by_epoch/3, replay_detect/2]). -export([init_connection_state_seq/2, current_connection_state_epoch/2]). @@ -55,6 +55,8 @@ -type dtls_version() :: ssl_record:ssl_version(). -type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'. +-define(REPLAY_WINDOW_SIZE, 64). + -compile(inline). %%==================================================================== @@ -73,7 +75,7 @@ init_connection_states(Role, BeastMitigation) -> Initial = initial_connection_state(ConnectionEnd, BeastMitigation), Current = Initial#{epoch := 0}, InitialPending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), - Pending = InitialPending#{epoch => undefined}, + Pending = InitialPending#{epoch => undefined, replay_window => init_replay_window(?REPLAY_WINDOW_SIZE)}, #{saved_read => Current, current_read => Current, pending_read => Pending, @@ -96,11 +98,13 @@ save_current_connection_state(#{current_write := Current} = States, write) -> next_epoch(#{pending_read := Pending, current_read := #{epoch := Epoch}} = States, read) -> - States#{pending_read := Pending#{epoch := Epoch + 1}}; + States#{pending_read := Pending#{epoch := Epoch + 1, + replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}}; next_epoch(#{pending_write := Pending, current_write := #{epoch := Epoch}} = States, write) -> - States#{pending_write := Pending#{epoch := Epoch + 1}}. + States#{pending_write := Pending#{epoch := Epoch + 1, + replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}}. get_connection_state_by_epoch(Epoch, #{current_write := #{epoch := Epoch} = Current}, write) -> @@ -393,7 +397,7 @@ init_connection_state_seq(_, ConnnectionStates) -> integer(). %% %% Description: Returns the epoch the connection_state record -%% that is currently defined as the current conection state. +%% that is currently defined as the current connection state. %%-------------------------------------------------------------------- current_connection_state_epoch(#{current_read := #{epoch := Epoch}}, read) -> @@ -402,6 +406,16 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, write) -> Epoch. +-spec hello_version(dtls_version(), [dtls_version()]) -> dtls_version(). +hello_version(Version, Versions) -> + case dtls_v1:corresponding_tls_version(Version) of + TLSVersion when TLSVersion >= {3, 3} -> + Version; + _ -> + lowest_protocol_version(Versions) + end. + + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -410,6 +424,7 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> ssl_record:initial_security_params(ConnectionEnd), epoch => undefined, sequence_number => 0, + replay_window => init_replay_window(?REPLAY_WINDOW_SIZE), beast_mitigation => BeastMitigation, compression_state => undefined, cipher_state => undefined, @@ -439,43 +454,60 @@ encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment, encode_plain_text(Type, Version, Data, #{compression_state := CompS0, epoch := Epoch, sequence_number := Seq, + cipher_state := CipherS0, security_parameters := #security_parameters{ cipher_type = ?AEAD, + bulk_cipher_algorithm = + BulkCipherAlgo, compression_algorithm = CompAlg} } = WriteState0) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, AAD = calc_aad(Type, Version, Epoch, Seq), - ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, AAD); -encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + TLSVersion = dtls_v1:corresponding_tls_version(Version), + {CipherFragment, CipherS1} = + ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, Comp, TLSVersion), + {CipherFragment, WriteState0#{compression_state => CompS1, + cipher_state => CipherS1}}; +encode_plain_text(Type, Version, Fragment, #{compression_state := CompS0, epoch := Epoch, sequence_number := Seq, + cipher_state := CipherS0, security_parameters := - #security_parameters{compression_algorithm = CompAlg} + #security_parameters{compression_algorithm = CompAlg, + bulk_cipher_algorithm = + BulkCipherAlgo} }= WriteState0) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + {Comp, CompS1} = ssl_record:compress(CompAlg, Fragment, CompS0), WriteState1 = WriteState0#{compression_state => CompS1}, - MacHash = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp), - ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, MacHash). + MAC = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp), + TLSVersion = dtls_v1:corresponding_tls_version(Version), + {CipherFragment, CipherS1} = + ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MAC, Fragment, TLSVersion), + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. decode_cipher_text(#ssl_tls{type = Type, version = Version, epoch = Epoch, sequence_number = Seq, fragment = CipherFragment} = CipherText, #{compression_state := CompressionS0, + cipher_state := CipherS0, security_parameters := #security_parameters{ cipher_type = ?AEAD, + bulk_cipher_algorithm = + BulkCipherAlgo, compression_algorithm = CompAlg}} = ReadState0, ConnnectionStates0) -> AAD = calc_aad(Type, Version, Epoch, Seq), - case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version), - CipherFragment, ReadState0, AAD) of - {PlainFragment, ReadState1} -> + TLSVersion = dtls_v1:corresponding_tls_version(Version), + case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, CipherFragment, TLSVersion) of + {PlainFragment, CipherState} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ReadState = ReadState1#{compression_state => CompressionS1}, + ReadState0 = ReadState0#{compression_state => CompressionS1, + cipher_state => CipherState}, + ReadState = update_replay_window(Seq, ReadState0), ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; #alert{} = Alert -> @@ -498,7 +530,8 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ReadState = ReadState1#{compression_state => CompressionS1}, + ReadState2 = ReadState1#{compression_state => CompressionS1}, + ReadState = update_replay_window(Seq, ReadState2), ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; false -> @@ -509,8 +542,7 @@ calc_mac_hash(Type, Version, #{mac_secret := MacSecret, security_parameters := #security_parameters{mac_algorithm = MacAlg}}, Epoch, SeqNo, Fragment) -> Length = erlang:iolist_size(Fragment), - NewSeq = (Epoch bsl 48) + SeqNo, - mac_hash(Version, MacAlg, MacSecret, NewSeq, Type, + mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment). highest_protocol_version() -> @@ -523,10 +555,46 @@ sufficient_dtlsv1_2_crypto_support() -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). -mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, - Length, Fragment). - +mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) -> + Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), + ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>, + Fragment], + dtls_v1:hmac_hash(MacAlg, MacSecret, Value). + calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) -> - NewSeq = (Epoch bsl 48) + SeqNo, - <<NewSeq:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. + <<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. + +init_replay_window(Size) -> + #{size => Size, + top => Size, + bottom => 0, + mask => 0 bsl 64 + }. + +replay_detect(#ssl_tls{sequence_number = SequenceNumber}, #{replay_window := Window}) -> + is_replay(SequenceNumber, Window). + + +is_replay(SequenceNumber, #{bottom := Bottom}) when SequenceNumber < Bottom -> + true; +is_replay(SequenceNumber, #{size := Size, + top := Top, + bottom := Bottom, + mask := Mask}) when (SequenceNumber >= Bottom) andalso (SequenceNumber =< Top) -> + Index = (SequenceNumber rem Size), + (Index band Mask) == 1; + +is_replay(_, _) -> + false. + +update_replay_window(SequenceNumber, #{replay_window := #{size := Size, + top := Top, + bottom := Bottom, + mask := Mask0} = Window0} = ConnectionStates) -> + NoNewBits = SequenceNumber - Top, + Index = SequenceNumber rem Size, + Mask = (Mask0 bsl NoNewBits) bor Index, + Window = Window0#{top => SequenceNumber, + bottom => Bottom + NoNewBits, + mask => Mask}, + ConnectionStates#{replay_window := Window}. diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index 2a746d97f0..fbbd479428 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -79,30 +79,31 @@ socket(Pid, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pid, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}. -%% Vad göra med emulerade -setopts(gen_udp, #sslsocket{pid = {Socket, _}}, Options) -> - {SockOpts, _} = tls_socket:split_options(Options), - inet:setopts(Socket, SockOpts); -setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}, Options) -> - {SockOpts, _} = tls_socket:split_options(Options), - Transport:setopts(ListenSocket, SockOpts); +setopts(_, #sslsocket{pid = {udp, #config{udp_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = tls_socket:split_options(Options), + dtls_udp_listener:set_sock_opts(ListenPid, SplitOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_udp, Socket, Options) -> inet:setopts(Socket, Options); setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +getopts(_, #sslsocket{pid = {udp, #config{udp_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = tls_socket:split_options(Options), + dtls_udp_listener:get_sock_opts(ListenPid, SplitOpts); getopts(gen_udp, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), {ok, EmulatedOpts ++ SocketOpts}; -getopts(Transport, #sslsocket{pid = {ListenSocket, #config{emulated = EmOpts}}}, Options) -> +getopts(_Transport, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), - SocketOpts = tls_socket:get_socket_opts(ListenSocket, SockOptNames, Transport), + SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), {ok, EmulatedOpts ++ SocketOpts}; %%% Following clauses will not be called for emulated options, they are handled in the connection process +getopts(gen_udp, {_,{{_, _},Socket}}, Options) -> + inet:getopts(Socket, Options); getopts(gen_udp, {_,Socket}, Options) -> inet:getopts(Socket, Options); getopts(Transport, Socket, Options) -> diff --git a/lib/ssl/src/dtls_udp_listener.erl b/lib/ssl/src/dtls_udp_listener.erl index f0ace2d887..c789a32087 100644 --- a/lib/ssl/src/dtls_udp_listener.erl +++ b/lib/ssl/src/dtls_udp_listener.erl @@ -23,9 +23,11 @@ -behaviour(gen_server). +-include("ssl_internal.hrl"). + %% API -export([start_link/4, active_once/3, accept/2, sockname/1, close/1, - get_all_opts/1]). + get_all_opts/1, get_sock_opts/2, set_sock_opts/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -61,8 +63,12 @@ sockname(UDPConnection) -> call(UDPConnection, sockname). close(UDPConnection) -> call(UDPConnection, close). +get_sock_opts(UDPConnection, SplitSockOpts) -> + call(UDPConnection, {get_sock_opts, SplitSockOpts}). get_all_opts(UDPConnection) -> call(UDPConnection, get_all_opts). +set_sock_opts(UDPConnection, Opts) -> + call(UDPConnection, {set_sock_opts, Opts}). %%%=================================================================== %%% gen_server callbacks @@ -108,9 +114,21 @@ handle_call(close, _, #state{dtls_processes = Processes, end, queue:to_list(Accepters)), {reply, ok, State#state{close = true, accepters = queue:new()}} end; +handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listner = Socket, + emulated_options = EmOpts} = State) -> + case get_socket_opts(Socket, SocketOptNames) of + {ok, Opts} -> + {reply, {ok, emulated_opts_list(EmOpts, EmOptNames, []) ++ Opts}, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; handle_call(get_all_opts, _, #state{dtls_options = DTLSOptions, emulated_options = EmOpts} = State) -> - {reply, {ok, EmOpts, DTLSOptions}, State}. + {reply, {ok, EmOpts, DTLSOptions}, State}; +handle_call({set_sock_opts, {SocketOpts, NewEmOpts}}, _, #state{listner = Socket, emulated_options = EmOpts0} = State) -> + set_socket_opts(Socket, SocketOpts), + EmOpts = do_set_emulated_opts(NewEmOpts, EmOpts0), + {reply, ok, State#state{emulated_options = EmOpts}}. handle_cast({active_once, Client, Pid}, State0) -> State = handle_active_once(Client, Pid, State0), @@ -121,6 +139,18 @@ handle_info({udp, Socket, IP, InPortNo, _} = Msg, #state{listner = Socket} = Sta next_datagram(Socket), {noreply, State}; +%% UDP socket does not have a connection and should not receive an econnreset +%% This does however happens on on some windows versions. Just ignoring it +%% appears to make things work as expected! +handle_info({udp_error, Socket, econnreset = Error}, #state{listner = Socket} = State) -> + Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]), + error_logger:info_report(Report), + {noreply, State}; +handle_info({udp_error, Socket, Error}, #state{listner = Socket} = State) -> + Report = io_lib:format("SSL UDP Listener shutdown: Socket error: ~p ~n", [Error]), + error_logger:info_report(Report), + {noreply, State#state{close=true}}; + handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients, dtls_processes = Processes0, close = ListenClosed} = State) -> @@ -247,3 +277,28 @@ call(Server, Msg) -> exit:{{shutdown, _},_} -> {error, closed} end. + +set_socket_opts(_, []) -> + ok; +set_socket_opts(Socket, SocketOpts) -> + inet:setopts(Socket, SocketOpts). + +get_socket_opts(_, []) -> + {ok, []}; +get_socket_opts(Socket, SocketOpts) -> + inet:getopts(Socket, SocketOpts). + +do_set_emulated_opts([], Opts) -> + Opts; +do_set_emulated_opts([{mode, Value} | Rest], Opts) -> + do_set_emulated_opts(Rest, Opts#socket_options{mode = Value}); +do_set_emulated_opts([{active, Value} | Rest], Opts) -> + do_set_emulated_opts(Rest, Opts#socket_options{active = Value}). + +emulated_opts_list(_,[], Acc) -> + Acc; +emulated_opts_list( Opts, [mode | Rest], Acc) -> + emulated_opts_list(Opts, Rest, [{mode, Opts#socket_options.mode} | Acc]); +emulated_opts_list(Opts, [active | Rest], Acc) -> + emulated_opts_list(Opts, Rest, [{active, Opts#socket_options.active} | Acc]). + diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl index 7169477a82..51ee8ec047 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -21,8 +21,11 @@ -include("ssl_cipher.hrl"). --export([suites/1, all_suites/1, mac_hash/7, ecc_curves/1, - corresponding_tls_version/1, corresponding_dtls_version/1]). +-export([suites/1, all_suites/1, hmac_hash/3, ecc_curves/1, + corresponding_tls_version/1, corresponding_dtls_version/1, + cookie_secret/0, cookie_timeout/0]). + +-define(COOKIE_BASE_TIMEOUT, 30000). -spec suites(Minor:: 253|255) -> [ssl_cipher:cipher_suite()]. @@ -37,9 +40,8 @@ all_suites(Version) -> end, ssl_cipher:all_suites(corresponding_tls_version(Version))). -mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, - Length, Fragment). +hmac_hash(MacAlg, MacSecret, Value) -> + tls_v1:hmac_hash(MacAlg, MacSecret, Value). ecc_curves({_Major, Minor}) -> tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)). @@ -47,6 +49,13 @@ ecc_curves({_Major, Minor}) -> corresponding_tls_version({254, Minor}) -> {3, corresponding_minor_tls_version(Minor)}. +cookie_secret() -> + crypto:strong_rand_bytes(32). + +cookie_timeout() -> + %% Cookie will live for two timeouts periods + round(rand:uniform() * ?COOKIE_BASE_TIMEOUT/2). + corresponding_minor_tls_version(255) -> 2; corresponding_minor_tls_version(253) -> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 2eda9d9491..bfdd0c205b 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,7 +1,6 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"8.1.1">>, [{load_module, tls_connection, soft_purge, soft_purge, []}]}, {<<"8\\..*">>, [{restart_application, ssl}]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, @@ -10,7 +9,6 @@ {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"8.1.1">>, [{load_module, tls_connection, soft_purge, soft_purge, []}]}, {<<"8\\..*">>, [{restart_application, ssl}]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 9a106f9742..75eb308ba5 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -38,16 +38,13 @@ getopts/2, setopts/2, getstat/1, getstat/2 ]). %% SSL/TLS protocol handling --export([cipher_suites/0, cipher_suites/1, eccs/0, eccs/1, - connection_info/1, versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, + +-export([cipher_suites/0, cipher_suites/1, eccs/0, eccs/1, versions/0, + format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, connection_information/1, connection_information/2]). %% Misc -export([handle_options/2, tls_version/1]). --deprecated({negotiated_next_protocol, 1, next_major_release}). --deprecated({connection_info, 1, next_major_release}). - -include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). @@ -115,7 +112,7 @@ connect(Host, Port, Options) -> connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> try - {ok, Config} = handle_options(Options, client), + {ok, Config} = handle_options(Options, client, Host), case Config#config.connection_cb of tls_connection -> tls_socket:connect(Host,Port,Config,Timeout); @@ -307,7 +304,7 @@ controlling_process(#sslsocket{pid = {Listen, %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- connection_information(#sslsocket{pid = Pid}) when is_pid(Pid) -> - case ssl_connection:connection_information(Pid) of + case ssl_connection:connection_information(Pid, false) of {ok, Info} -> {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; Error -> @@ -323,8 +320,8 @@ connection_information(#sslsocket{pid = {udp,_}}) -> %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- -connection_information(#sslsocket{} = SSLSocket, Items) -> - case connection_information(SSLSocket) of +connection_information(#sslsocket{pid = Pid}, Items) when is_pid(Pid) -> + case ssl_connection:connection_information(Pid, include_security_info(Items)) of {ok, Info} -> {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), Value =/= undefined]}; @@ -333,21 +330,6 @@ connection_information(#sslsocket{} = SSLSocket, Items) -> end. %%-------------------------------------------------------------------- -%% Deprecated --spec connection_info(#sslsocket{}) -> {ok, {tls_record:tls_atom_version(), ssl_cipher:erl_cipher_suite()}} | - {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -connection_info(#sslsocket{} = SSLSocket) -> - case connection_information(SSLSocket) of - {ok, Result} -> - {ok, {proplists:get_value(protocol, Result), proplists:get_value(cipher_suite, Result)}}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- -spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. %% %% Description: same as inet:peername/1. @@ -392,20 +374,6 @@ negotiated_protocol(#sslsocket{pid = Pid}) -> ssl_connection:negotiated_protocol(Pid). %%-------------------------------------------------------------------- --spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the next protocol that has been negotiated. If no -%% protocol has been negotiated will return {error, next_protocol_not_negotiated} -%%-------------------------------------------------------------------- -negotiated_next_protocol(Socket) -> - case negotiated_protocol(Socket) of - {error, protocol_not_negotiated} -> - {error, next_protocol_not_negotiated}; - Res -> - Res - end. - -%%-------------------------------------------------------------------- -spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()]. %%-------------------------------------------------------------------- cipher_suites() -> @@ -459,6 +427,16 @@ eccs_filter_supported(Curves) -> %%-------------------------------------------------------------------- getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> ssl_connection:get_opts(Pid, OptionTags); +getopts(#sslsocket{pid = {udp, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> + try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of + {ok, _} = Result -> + Result; + {error, InetError} -> + {error, {options, {socket_options, OptionTags, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, OptionTags, Error}}} + end; getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> try tls_socket:getopts(Transport, ListenSocket, OptionTags) of @@ -487,7 +465,16 @@ setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> _:_ -> {error, {options, {not_a_proplist, Options0}}} end; - +setopts(#sslsocket{pid = {udp, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) -> + try dtls_socket:setopts(Transport, ListenSocket, Options) of + ok -> + ok; + {error, InetError} -> + {error, {options, {socket_options, Options, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, Options, Error}}} + end; setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) -> try tls_socket:setopts(Transport, ListenSocket, Options) of ok -> @@ -555,19 +542,6 @@ sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) tls_socket:sockname(Transport, Socket). %%--------------------------------------------------------------- --spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns list of session info currently [{session_id, session_id(), -%% {cipher_suite, cipher_suite()}] -%%-------------------------------------------------------------------- -session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - ssl_connection:session_info(Pid); -session_info(#sslsocket{pid = {udp,_}}) -> - {error, enotconn}; -session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. - -%%--------------------------------------------------------------- -spec versions() -> [{ssl_app, string()} | {supported, [tls_record:tls_atom_version()]} | {available, [tls_record:tls_atom_version()]}]. %% @@ -677,8 +651,12 @@ do_listen(Port, #config{transport_info = {Transport, _, _, _}} = Config, dtls_c %% Handle extra ssl options given to ssl_accept -spec handle_options([any()], #ssl_options{}) -> #ssl_options{} ; ([any()], client | server) -> {ok, #config{}}. +handle_options(Opts, Role) -> + handle_options(Opts, Role, undefined). + + handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, - cacertfile = CaCertFile0} = InheritedSslOpts) -> + cacertfile = CaCertFile0} = InheritedSslOpts, _) -> RecordCB = record_cb(Protocol), CaCerts = handle_option(cacerts, Opts0, CaCerts0), {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, @@ -711,7 +689,7 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, end; %% Handle all options in listen and connect -handle_options(Opts0, Role) -> +handle_options(Opts0, Role, Host) -> Opts = proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Opts0), assert_proplist(Opts), @@ -783,7 +761,9 @@ handle_options(Opts0, Role) -> make_next_protocol_selector( handle_option(client_preferred_next_protocols, Opts, undefined)), log_alert = handle_option(log_alert, Opts, true), - server_name_indication = handle_option(server_name_indication, Opts, undefined), + server_name_indication = handle_option(server_name_indication, Opts, + default_option_role(client, + server_name_indication_default(Host), Role)), sni_hosts = handle_option(sni_hosts, Opts, []), sni_fun = handle_option(sni_fun, Opts, undefined), honor_cipher_order = handle_option(honor_cipher_order, Opts, @@ -1027,12 +1007,20 @@ validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> validate_option(next_protocols_advertised, undefined) -> undefined; -validate_option(server_name_indication, Value) when is_list(Value) -> +validate_option(server_name_indication = Opt, Value) when is_list(Value) -> + %% RFC 6066, Section 3: Currently, the only server names supported are + %% DNS hostnames + case inet_parse:domain(Value) of + false -> + throw({error, {options, {{Opt, Value}}}}); + true -> + Value + end; +validate_option(server_name_indication, undefined = Value) -> Value; validate_option(server_name_indication, disable) -> - disable; -validate_option(server_name_indication, undefined) -> undefined; + validate_option(sni_hosts, []) -> []; validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) -> @@ -1076,15 +1064,15 @@ validate_option(protocol, Value = dtls) -> validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). -handle_hashsigns_option(Value, {Major, Minor} = Version) when is_list(Value) - andalso Major >= 3 andalso Minor >= 3-> +handle_hashsigns_option(Value, Version) when is_list(Value) + 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(_, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3-> +handle_hashsigns_option(_, Version) when Version >= {3, 3} -> handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version); handle_hashsigns_option(_, _Version) -> undefined. @@ -1118,7 +1106,7 @@ validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; Version == sslv3 -> tls_validate_versions(Rest, Versions); validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; - Version == 'dtlsv2'-> + Version == 'dtlsv1.2'-> dtls_validate_versions(Rest, Versions); validate_versions([Ver| _], Versions) -> throw({error, {options, {Ver, {versions, Versions}}}}). @@ -1136,7 +1124,7 @@ tls_validate_versions([Ver| _], Versions) -> dtls_validate_versions([], Versions) -> Versions; dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; - Version == 'dtlsv2'-> + Version == 'dtlsv1.2'-> dtls_validate_versions(Rest, Versions); dtls_validate_versions([Ver| _], Versions) -> throw({error, {options, {Ver, {versions, Versions}}}}). @@ -1480,3 +1468,18 @@ default_cb_info(tls) -> {gen_tcp, tcp, tcp_closed, tcp_error}; default_cb_info(dtls) -> {gen_udp, udp, udp_closed, udp_error}. + +include_security_info([]) -> + false; +include_security_info([Item | Items]) -> + case lists:member(Item, [client_random, server_random, master_secret]) of + true -> + true; + false -> + include_security_info(Items) + end. + +server_name_indication_default(Host) when is_list(Host) -> + Host; +server_name_indication_default(_) -> + undefined. diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 696a55e4b9..b923785e17 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -57,16 +57,16 @@ decode(Bin) -> reason_code(#alert{description = ?CLOSE_NOTIFY}, _) -> closed; reason_code(#alert{description = Description}, _) -> - {tls_alert, description_txt(Description)}. + {tls_alert, string:to_lower(description_txt(Description))}. %%-------------------------------------------------------------------- -spec alert_txt(#alert{}) -> string(). %% %% Description: Returns the error string for given alert. %%-------------------------------------------------------------------- -alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined}) -> - Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++ - level_txt(Level) ++" "++ description_txt(Description); +alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined, role = Role}) -> + "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " " ++ string:to_upper(atom_to_list(Role)) ++ " ALERT: " ++ + level_txt(Level) ++ description_txt(Description); alert_txt(#alert{reason = Reason} = Alert) -> BaseTxt = alert_txt(Alert#alert{reason = undefined}), FormatDepth = 9, % Some limit on printed representation of an error @@ -93,73 +93,73 @@ decode(<<>>, Acc, _) -> lists:reverse(Acc, []). level_txt(?WARNING) -> - "Warning:"; + "Warning - "; level_txt(?FATAL) -> - "Fatal error:". + "Fatal - ". description_txt(?CLOSE_NOTIFY) -> - "close notify"; + "Close Notify"; description_txt(?UNEXPECTED_MESSAGE) -> - "unexpected message"; + "Unexpected Message"; description_txt(?BAD_RECORD_MAC) -> - "bad record mac"; + "Bad Record MAC"; description_txt(?DECRYPTION_FAILED) -> - "decryption failed"; + "Decryption Failed"; description_txt(?RECORD_OVERFLOW) -> - "record overflow"; + "Record Overflow"; description_txt(?DECOMPRESSION_FAILURE) -> - "decompression failure"; + "Decompression Failure"; description_txt(?HANDSHAKE_FAILURE) -> - "handshake failure"; + "Handshake Failure"; description_txt(?NO_CERTIFICATE_RESERVED) -> - "No certificate reserved"; + "No Certificate Reserved"; description_txt(?BAD_CERTIFICATE) -> - "bad certificate"; + "Bad Certificate"; description_txt(?UNSUPPORTED_CERTIFICATE) -> - "unsupported certificate"; + "Unsupported Certificate"; description_txt(?CERTIFICATE_REVOKED) -> - "certificate revoked"; + "Certificate Revoked"; description_txt(?CERTIFICATE_EXPIRED) -> - "certificate expired"; + "Certificate Expired"; description_txt(?CERTIFICATE_UNKNOWN) -> - "certificate unknown"; + "Certificate Unknown"; description_txt(?ILLEGAL_PARAMETER) -> - "illegal parameter"; + "Illegal Parameter"; description_txt(?UNKNOWN_CA) -> - "unknown ca"; + "Unknown CA"; description_txt(?ACCESS_DENIED) -> - "access denied"; + "Access Denied"; description_txt(?DECODE_ERROR) -> - "decode error"; + "Decode Error"; description_txt(?DECRYPT_ERROR) -> - "decrypt error"; + "Decrypt Error"; description_txt(?EXPORT_RESTRICTION) -> - "export restriction"; + "Export Restriction"; description_txt(?PROTOCOL_VERSION) -> - "protocol version"; + "Protocol Version"; description_txt(?INSUFFICIENT_SECURITY) -> - "insufficient security"; + "Insufficient Security"; description_txt(?INTERNAL_ERROR) -> - "internal error"; + "Internal Error"; description_txt(?USER_CANCELED) -> - "user canceled"; + "User Canceled"; description_txt(?NO_RENEGOTIATION) -> - "no renegotiation"; + "No Renegotiation"; description_txt(?UNSUPPORTED_EXTENSION) -> - "unsupported extension"; + "Unsupported Extension"; description_txt(?CERTIFICATE_UNOBTAINABLE) -> - "certificate unobtainable"; + "Certificate Unobtainable"; description_txt(?UNRECOGNISED_NAME) -> - "unrecognised name"; + "Unrecognised Name"; description_txt(?BAD_CERTIFICATE_STATUS_RESPONSE) -> - "bad certificate status response"; + "Bad Certificate Status Response"; description_txt(?BAD_CERTIFICATE_HASH_VALUE) -> - "bad certificate hash value"; + "Bad Certificate Hash Value"; description_txt(?UNKNOWN_PSK_IDENTITY) -> - "unknown psk identity"; + "Unknown Psk Identity"; description_txt(?INAPPROPRIATE_FALLBACK) -> - "inappropriate fallback"; + "Inappropriate Fallback"; description_txt(?NO_APPLICATION_PROTOCOL) -> - "no application protocol"; + "No application protocol"; description_txt(Enum) -> lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])). diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl index f3743ba0f0..1aabb6c55a 100644 --- a/lib/ssl/src/ssl_alert.hrl +++ b/lib/ssl/src/ssl_alert.hrl @@ -118,6 +118,7 @@ level, description, where = {?FILE, ?LINE}, + role, reason }). -endif. % -ifdef(ssl_alert). diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 2046ec75b3..0dd5e5c5cf 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -125,7 +125,7 @@ file_to_crls(File, DbHandle) -> %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', - extnValue = KeyUse}}, UserState = {Role, _,_, _, _}) -> + extnValue = KeyUse}}, UserState = {Role, _,_, _, _, _}) -> case is_valid_extkey_usage(KeyUse, Role) of true -> {valid, UserState}; @@ -138,8 +138,15 @@ validate(_, {bad_cert, _} = Reason, _) -> {fail, Reason}; validate(_, valid, UserState) -> {valid, UserState}; -validate(_, valid_peer, 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(_, valid_peer, UserState) -> + {valid, UserState}. %%-------------------------------------------------------------------- -spec is_valid_key_usage(list(), term()) -> boolean(). diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 32f05628bb..bd60197c88 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -40,7 +40,7 @@ ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0, rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, - random_bytes/1, calc_aad/3, calc_mac_hash/4, + random_bytes/1, calc_mac_hash/4, is_stream_ciphersuite/1]). -export_type([cipher_suite/0, @@ -157,7 +157,7 @@ cipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) -> aead_cipher(chacha20_poly1305, #cipher_state{key=Key} = CipherState, SeqNo, AAD0, Fragment, _Version) -> CipherLen = erlang:iolist_size(Fragment), AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, - Nonce = <<SeqNo:64/integer>>, + Nonce = ?uint64(SeqNo), {Content, CipherTag} = crypto:block_encrypt(chacha20_poly1305, Key, Nonce, {AAD, Fragment}), {<<Content/binary, CipherTag/binary>>, CipherState}; aead_cipher(Type, #cipher_state{key=Key, iv = IV0, nonce = Nonce} = CipherState, _SeqNo, AAD0, Fragment, _Version) -> @@ -280,7 +280,7 @@ aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version CipherLen = size(Fragment) - 16, <<CipherText:CipherLen/bytes, CipherTag:16/bytes>> = Fragment, AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, - Nonce = <<SeqNo:64/integer>>, + Nonce = ?uint64(SeqNo), {Nonce, AAD, CipherText, CipherTag}; aead_ciphertext_to_state(_, _SeqNo, <<Salt:4/bytes, _/binary>>, AAD0, Fragment, _Version) -> CipherLen = size(Fragment) - 24, @@ -1531,10 +1531,6 @@ is_fallback(CipherSuites)-> random_bytes(N) -> crypto:strong_rand_bytes(N). -calc_aad(Type, {MajVer, MinVer}, - #{sequence_number := SeqNo}) -> - <<SeqNo:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. - calc_mac_hash(Type, Version, PlainFragment, #{sequence_number := SeqNo, mac_secret := MacSecret, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index ad220ae9de..0163d08f2a 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -42,9 +42,9 @@ %% User Events -export([send/2, recv/3, close/2, shutdown/2, - new_user/2, get_opts/2, set_opts/2, session_info/1, + new_user/2, get_opts/2, set_opts/2, peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5, - connection_information/1, handle_common_event/5 + connection_information/2, handle_common_event/5 ]). %% General gen_statem state functions with extra callback argument @@ -185,12 +185,12 @@ recv(Pid, Length, Timeout) -> call(Pid, {recv, Length, Timeout}). %%-------------------------------------------------------------------- --spec connection_information(pid()) -> {ok, list()} | {error, reason()}. +-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}. %% %% Description: Get the SNI hostname %%-------------------------------------------------------------------- -connection_information(Pid) when is_pid(Pid) -> - call(Pid, connection_information). +connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> + call(Pid, {connection_information, IncludeSecrityInfo}). %%-------------------------------------------------------------------- -spec close(pid(), {close, Timeout::integer() | @@ -247,14 +247,6 @@ set_opts(ConnectionPid, Options) -> call(ConnectionPid, {set_opts, Options}). %%-------------------------------------------------------------------- --spec session_info(pid()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns info about the ssl session -%%-------------------------------------------------------------------- -session_info(ConnectionPid) -> - call(ConnectionPid, session_info). - -%%-------------------------------------------------------------------- -spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. %% %% Description: Returns the peer cert @@ -509,13 +501,7 @@ certify(internal, #certificate{} = Cert, crl_db = CRLDbInfo, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, - Opts#ssl_options.depth, - Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, - Opts#ssl_options.partial_chain, - Opts#ssl_options.crl_check, - CRLDbInfo, - Role) of + Opts, CRLDbInfo, Role) of {PeerCert, PublicKeyInfo} -> handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); @@ -775,14 +761,12 @@ connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, connection({call, From}, peer_certificate, #state{session = #session{peer_certificate = Cert}} = State, _) -> hibernate_after(connection, State, [{reply, From, {ok, Cert}}]); -connection({call, From}, connection_information, State, _) -> +connection({call, From}, {connection_information, true}, State, _) -> + Info = connection_info(State) ++ security_info(State), + hibernate_after(connection, State, [{reply, From, {ok, Info}}]); +connection({call, From}, {connection_information, false}, State, _) -> Info = connection_info(State), hibernate_after(connection, State, [{reply, From, {ok, Info}}]); -connection({call, From}, session_info, #state{session = #session{session_id = Id, - cipher_suite = Suite}} = State, _) -> - SessionInfo = [{session_id, Id}, - {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}], - hibernate_after(connection, State, [{reply, From, SessionInfo}]); connection({call, From}, negotiated_protocol, #state{negotiated_protocol = undefined} = State, _) -> hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]); @@ -921,14 +905,14 @@ handle_call({new_user, User}, From, StateName, handle_call({get_opts, OptTags}, From, _, #state{socket = Socket, transport_cb = Transport, - socket_options = SockOpts}, _) -> - OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), + socket_options = SockOpts}, Connection) -> + OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), {keep_state_and_data, [{reply, From, OptsReply}]}; handle_call({set_opts, Opts0}, From, StateName, #state{socket_options = Opts1, socket = Socket, - transport_cb = Transport} = State0, _) -> - {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []), + transport_cb = Transport} = State0, Connection) -> + {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), State = State0#state{socket_options = Opts}, handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); @@ -1019,7 +1003,7 @@ terminate(_, _, #state{terminated = true}) -> %% Happens when user closes the connection using ssl:close/1 %% we want to guarantee that Transport:close has been called %% when ssl:close/1 returns unless it is a downgrade where - %% we want to guarantee that close alert is recived before + %% we want to guarantee that close alert is received before %% returning. In both cases terminate has been run manually %% before run by gen_statem which will end up here ok; @@ -1159,7 +1143,7 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, port = Port, session = Session, user_application = {_Mon, Pid}, role = Role, socket_options = Opts, tracker = Tracker}) -> invalidate_session(Role, Host, Port, Session), - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + log_alert(SslOpts#ssl_options.log_alert, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), {stop, normal}; @@ -1169,15 +1153,16 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, {stop, {shutdown, peer_close}}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, renegotiation = {true, internal}} = State) -> + log_alert(SslOpts#ssl_options.log_alert, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), handle_normal_shutdown(Alert, StateName, State), {stop, {shutdown, peer_close}}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{ssl_options = SslOpts, renegotiation = {true, From}, + #state{role = Role, + ssl_options = SslOpts, renegotiation = {true, From}, protocol_cb = Connection} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + log_alert(SslOpts#ssl_options.log_alert, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), {Record, State} = Connection:next_record(State0), %% Go back to connection! @@ -1185,8 +1170,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, %% Gracefully log and ignore all other warning alerts handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{ssl_options = SslOpts, protocol_cb = Connection} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) -> + log_alert(SslOpts#ssl_options.log_alert, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), {Record, State} = Connection:next_record(State0), Connection:next_event(StateName, Record, State). @@ -1194,7 +1179,8 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName, %%% Internal functions %%-------------------------------------------------------------------- connection_info(#state{sni_hostname = SNIHostname, - session = #session{cipher_suite = CipherSuite, ecc = ECCCurve}, + session = #session{session_id = SessionId, + cipher_suite = CipherSuite, ecc = ECCCurve}, protocol_cb = Connection, negotiated_version = {_,_} = Version, ssl_options = Opts}) -> @@ -1209,9 +1195,18 @@ connection_info(#state{sni_hostname = SNIHostname, [] end, [{protocol, RecordCB:protocol_version(Version)}, + {session_id, SessionId}, {cipher_suite, CipherSuiteDef}, {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts). +security_info(#state{connection_states = ConnectionStates}) -> + #{security_parameters := + #security_parameters{client_random = ClientRand, + server_random = ServerRand, + master_secret = MasterSecret}} = + ssl_record:current_connection_state(ConnectionStates, read), + [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]. + do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, #state{negotiated_version = Version, @@ -1688,7 +1683,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, ssl_record:pending_connection_state(ConnectionStates0, read), TLSVersion = ssl:tls_version(Version), HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, - TLSVersion, [TLSVersion]), + TLSVersion), Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, TLSVersion), State = Connection:queue_handshake(Msg, State0), @@ -1916,42 +1911,39 @@ call(FsmPid, Event) -> {error, closed} end. -get_socket_opts(_,_,[], _, Acc) -> +get_socket_opts(_, _,_,[], _, Acc) -> {ok, Acc}; -get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) -> +get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> case SockOpts#socket_options.packet of {Type, headers} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); Type -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) end; -get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - try tls_socket:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Error} -> - {error, {options, {socket_options, Tag, Error}}} - catch - %% So that inet behavior does not crash our process - _:Error -> {error, {options, {socket_options, Tag, Error}}} +get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> + case Connection:getopts(Transport, Socket, [Tag]) of + {ok, [Opt]} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); + {error, Reason} -> + {error, {options, {socket_options, Tag, Reason}}} end; -get_socket_opts(_, _,Opts, _,_) -> +get_socket_opts(_,_, _,Opts, _,_) -> {error, {options, {socket_options, Opts, function_clause}}}. -set_socket_opts(_,_, [], SockOpts, []) -> +set_socket_opts(_,_,_, [], SockOpts, []) -> {ok, SockOpts}; -set_socket_opts(Transport, Socket, [], SockOpts, Other) -> +set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> %% Set non emulated options - try tls_socket:setopts(Transport, Socket, Other) of + try ConnectionCb:setopts(Transport, Socket, Other) of ok -> {ok, SockOpts}; {error, InetError} -> @@ -1962,13 +1954,13 @@ set_socket_opts(Transport, Socket, [], SockOpts, Other) -> {{error, {options, {socket_options, Other, Error}}}, SockOpts} end; -set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) +set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary -> - set_socket_opts(Transport, Socket, Opts, + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> +set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) +set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw; Packet == 0; Packet == 1; @@ -1984,26 +1976,26 @@ set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) Packet == httph; Packet == http_bin; Packet == httph_bin -> - set_socket_opts(Transport, Socket, Opts, + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> +set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) +set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) -> - set_socket_opts(Transport, Socket, Opts, + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> +set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) -> {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once; Active == true; Active == false -> - set_socket_opts(Transport, Socket, Opts, + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{active = Active}, Other); -set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> +set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]). +set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). start_or_recv_cancel_timer(infinity, _RecvFrom) -> undefined; @@ -2379,18 +2371,19 @@ alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connectio Transport, Socket, Connection, Tracker), ReasonCode}) end. -log_alert(true, Info, Alert) -> +log_alert(true, ProtocolName, StateName, Alert) -> Txt = ssl_alert:alert_txt(Alert), - error_logger:format("SSL: ~p: ~s\n", [Info, Txt]); -log_alert(false, _, _) -> + error_logger:format("~s: In state ~p ~s\n", [ProtocolName, StateName, Txt]); +log_alert(false, _, _, _) -> ok. handle_own_alert(Alert, Version, StateName, - #state{transport_cb = Transport, - socket = Socket, - protocol_cb = Connection, - connection_states = ConnectionStates, - ssl_options = SslOpts} = State) -> + #state{role = Role, + transport_cb = Transport, + socket = Socket, + protocol_cb = Connection, + connection_states = ConnectionStates, + ssl_options = SslOpts} = State) -> try %% Try to tell the other side {BinMsg, _} = Connection:encode_alert(Alert, Version, ConnectionStates), @@ -2399,7 +2392,7 @@ handle_own_alert(Alert, Version, StateName, ignore end, try %% Try to tell the local user - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + log_alert(SslOpts#ssl_options.log_alert, Connection:protocol_name(), StateName, Alert#alert{role = Role}), handle_normal_shutdown(Alert,StateName, State) catch _:_ -> ok diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index b597c059af..3e26f67de1 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -91,7 +91,8 @@ %% underlaying packet format. Introduced by DTLS - RFC 4347. %% 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. + flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. + protocol_specific = #{} :: map() }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index e84473f215..b1661624b5 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/10, client_certificate_verify/6, certificate_verify/6, verify_signature/5, +-export([certify/6, 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 ]). @@ -64,11 +64,11 @@ ]). %% Cipher suites handling --export([available_suites/2, available_signature_algs/3, cipher_suites/2, +-export([available_suites/2, available_signature_algs/2, cipher_suites/2, select_session/11, supported_ecc/1, available_signature_algs/4]). %% Extensions handling --export([client_hello_extensions/6, +-export([client_hello_extensions/5, handle_client_hello_extensions/9, %% Returns server hello extensions handle_server_hello_extensions/9, select_curve/2, select_curve/3 ]). @@ -119,10 +119,9 @@ server_hello(SessionId, Version, ConnectionStates, Extensions) -> server_hello_done() -> #server_hello_done{}. -client_hello_extensions(Host, Version, CipherSuites, +client_hello_extensions(Version, CipherSuites, #ssl_options{signature_algs = SupportedHashSigns, - eccs = SupportedECCs, - versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) -> + eccs = SupportedECCs} = SslOpts, ConnectionStates, Renegotiation) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of true -> @@ -136,14 +135,14 @@ client_hello_extensions(Host, Version, CipherSuites, renegotiation_info = renegotiation_info(tls_record, client, ConnectionStates, Renegotiation), srp = SRP, - signature_algs = available_signature_algs(SupportedHashSigns, Version, AllVersions), + signature_algs = available_signature_algs(SupportedHashSigns, Version), ec_point_formats = EcPointFormats, elliptic_curves = EllipticCurves, alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), next_protocol_negotiation = encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation), - sni = sni(Host, SslOpts#ssl_options.server_name_indication)}. + sni = sni(SslOpts#ssl_options.server_name_indication)}. %%-------------------------------------------------------------------- -spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. @@ -389,24 +388,26 @@ verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, %%-------------------------------------------------------------------- --spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, - verify_peer | verify_none, {fun(), term}, fun(), term(), term(), +-spec certify(#certificate{}, db_handle(), certdb_ref(), #ssl_options{}, term(), client | server) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - MaxPathLen, _Verify, ValidationFunAndState0, PartialChain, CRLCheck, CRLDbHandle, Role) -> + Opts, CRLDbHandle, Role) -> + [PeerCert | _] = ASN1Certs, try {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, PartialChain), - ValidationFunAndState = validation_fun_and_state(ValidationFunAndState0, Role, + 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, - CRLCheck, CRLDbHandle, CertPath), + Opts#ssl_options.server_name_indication, + Opts#ssl_options.crl_check, CRLDbHandle, CertPath), case public_key:pkix_path_validation(TrustedCert, CertPath, - [{max_path_length, MaxPathLen}, + [{max_path_length, Opts#ssl_options.depth}, {verify_fun, ValidationFunAndState}]) of {ok, {PublicKeyInfo,_}} -> {PeerCert, PublicKeyInfo}; @@ -414,9 +415,11 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, path_validation_alert(Reason) end catch - error:_ -> + error:{badmatch,{asn1, Asn1Reason}} -> %% ASN-1 decode of certificate somehow failed - ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, failed_to_decode_certificate) + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason}); + error:OtherReason -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) end. %%-------------------------------------------------------------------- @@ -1523,25 +1526,16 @@ select_shared_curve([Curve | Rest], Curves) -> select_shared_curve(Rest, Curves) end. -%% RFC 6066, Section 3: Currently, the only server names supported are -%% DNS hostnames -sni(_, disable) -> +sni(undefined) -> undefined; -sni(Host, undefined) -> - sni1(Host); -sni(_Host, SNIOption) -> - sni1(SNIOption). - -sni1(Hostname) -> - case inet_parse:domain(Hostname) of - false -> undefined; - true -> #sni{hostname = Hostname} - end. +sni(Hostname) -> + #sni{hostname = Hostname}. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, - CRLCheck, CRLDbHandle, CertPath) -> + ServerNameIndication, CRLCheck, CRLDbHandle, CertPath) -> {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> case ssl_certificate:validate(OtpCert, Extension, @@ -1558,9 +1552,9 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, (OtpCert, VerifyResult, {SslState, UserState}) -> apply_user_fun(Fun, OtpCert, VerifyResult, UserState, SslState, CertPath) - end, {{Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}, UserState0}}; + end, {{Role, CertDbHandle, CertDbRef, ServerNameIndication, CRLCheck, CRLDbHandle}, UserState0}}; validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, - CRLCheck, CRLDbHandle, CertPath) -> + ServerNameIndication, CRLCheck, CRLDbHandle, CertPath) -> {fun(OtpCert, {extension, _} = Extension, SslState) -> ssl_certificate:validate(OtpCert, Extension, @@ -1569,8 +1563,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, (VerifyResult == valid_peer) -> case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, CRLDbHandle, VerifyResult, CertPath) of - valid -> - {VerifyResult, SslState}; + valid -> + ssl_certificate:validate(OtpCert, + VerifyResult, + SslState); Reason -> {fail, Reason} end; @@ -1578,10 +1574,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, ssl_certificate:validate(OtpCert, VerifyResult, SslState) - end, {Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}}. + end, {Role, CertDbHandle, CertDbRef, ServerNameIndication, CRLCheck, CRLDbHandle}}. apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, - {_, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle} = SslState, CertPath) when + {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath) when (VerifyResult == valid) or (VerifyResult == valid_peer) -> case Fun(OtpCert, VerifyResult, UserState0) of {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> @@ -1617,8 +1613,11 @@ path_validation_alert({bad_cert, unknown_critical_extension}) -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); path_validation_alert({bad_cert, {revoked, _}}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); -path_validation_alert({bad_cert, revocation_status_undetermined}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +%%path_validation_alert({bad_cert, revocation_status_undetermined}) -> +%% ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) -> + Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE), + Alert#alert{reason = Details}; path_validation_alert({bad_cert, selfsigned_peer}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, unknown_ca}) -> @@ -2150,16 +2149,11 @@ is_member(Suite, SupportedSuites) -> select_compression(_CompressionMetodes) -> ?NULL. -available_signature_algs(undefined, _, _) -> +available_signature_algs(undefined, _) -> undefined; -available_signature_algs(SupportedHashSigns, {Major, Minor}, AllVersions) when Major >= 3 andalso Minor >= 3 -> - case tls_record:lowest_protocol_version(AllVersions) of - {3, 3} -> - #hash_sign_algos{hash_sign_algos = SupportedHashSigns}; - _ -> - undefined - end; -available_signature_algs(_, _, _) -> +available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} -> + #hash_sign_algos{hash_sign_algos = SupportedHashSigns}; +available_signature_algs(_, _) -> undefined. psk_secret(PSKIdentity, PSKLookup) -> @@ -2200,7 +2194,8 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath, DBInfo}) end, {CertDbHandle, CertDbRef}}}, - {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end} + {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end}, + {undetermined_details, true} ], case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of no_dps -> @@ -2210,7 +2205,7 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined} case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of - {bad_cert, revocation_status_undetermined} -> + {bad_cert, {revocation_status_undetermined, _}} -> crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer), Options); Other -> @@ -2220,7 +2215,7 @@ crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, C crl_check_same_issuer(OtpCert, best_effort, Dps, Options) -> case public_key:pkix_crls_validate(OtpCert, Dps, Options) of - {bad_cert, revocation_status_undetermined} -> + {bad_cert, {revocation_status_undetermined, _}} -> valid; Other -> Other @@ -2306,6 +2301,8 @@ is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) - is_acceptable_hash_sign(Algos, SupportedHashSigns); is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) -> is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdh_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) -> is_acceptable_hash_sign(Algos, SupportedHashSigns); is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when @@ -2346,11 +2343,11 @@ bad_key(#'RSAPrivateKey'{}) -> bad_key(#'ECPrivateKey'{}) -> unacceptable_ecdsa_key. -available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when - (Major >= 3) andalso (Minor >= 3) -> +available_signature_algs(undefined, SupportedHashSigns, _, Version) when + Version >= {3,3} -> SupportedHashSigns; available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns, - _, {Major, Minor}) when (Major >= 3) andalso (Minor >= 3) -> + _, Version) when Version >= {3,3} -> sets:to_list(sets:intersection(sets:from_list(ClientHashSigns), sets:from_list(SupportedHashSigns))); available_signature_algs(_, _, _, _) -> diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index ac212a56d8..24ac34653e 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -144,7 +144,7 @@ honor_ecc_order :: boolean(), v2_hello_compatible :: boolean(), max_handshake_size :: integer() - }). + }). -record(socket_options, { diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index b10069c3cb..62c2ffce8b 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -45,11 +45,7 @@ -export([compress/3, uncompress/3, compressions/0]). %% Payload encryption/decryption --export([cipher/4, decipher/4, is_correct_mac/2, - cipher_aead/4, decipher_aead/4]). - -%% Encoding --export([encode_plain_text/4]). +-export([cipher/4, decipher/4, cipher_aead/4, is_correct_mac/2]). -export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]). @@ -67,7 +63,7 @@ connection_state(). %% %% Description: Returns the instance of the connection_state map -%% that is currently defined as the current conection state. +%% that is currently defined as the current connection state. %%-------------------------------------------------------------------- current_connection_state(ConnectionStates, read) -> maps:get(current_read, ConnectionStates); @@ -79,7 +75,7 @@ current_connection_state(ConnectionStates, write) -> connection_state(). %% %% Description: Returns the instance of the connection_state map -%% that is pendingly defined as the pending conection state. +%% that is pendingly defined as the pending connection state. %%-------------------------------------------------------------------- pending_connection_state(ConnectionStates, read) -> maps:get(pending_read, ConnectionStates); @@ -271,26 +267,6 @@ set_pending_cipher_state(#{pending_read := Read, pending_read => Read#{cipher_state => ServerState}, pending_write => Write#{cipher_state => ClientState}}. -encode_plain_text(Type, Version, Data, #{compression_state := CompS0, - security_parameters := - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm = CompAlg} - } = WriteState0) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - AAD = ssl_cipher:calc_aad(Type, Version, WriteState1), - ssl_record:cipher_aead(Version, Comp, WriteState1, AAD); -encode_plain_text(Type, Version, Data, #{compression_state := CompS0, - security_parameters := - #security_parameters{compression_algorithm = CompAlg} - }= WriteState0) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1), - ssl_record:cipher(Version, Comp, WriteState1, MacHash); -encode_plain_text(_,_,_,CS) -> - exit({cs, CS}). uncompress(?NULL, Data, CS) -> {Data, CS}. @@ -322,12 +298,12 @@ cipher(Version, Fragment, {CipherFragment, CipherS1} = ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), {CipherFragment, WriteState0#{cipher_state => CipherS1}}. -%%-------------------------------------------------------------------- --spec cipher_aead(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> - {CipherFragment::binary(), connection_state()}. -%% -%% Description: Payload encryption -%%-------------------------------------------------------------------- +%% %%-------------------------------------------------------------------- +%% -spec cipher_aead(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> +%% {CipherFragment::binary(), connection_state()}. +%% %% +%% %% Description: Payload encryption +%% %%-------------------------------------------------------------------- cipher_aead(Version, Fragment, #{cipher_state := CipherS0, sequence_number := SeqNo, @@ -341,7 +317,8 @@ cipher_aead(Version, Fragment, {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- --spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> {binary(), binary(), connection_state} | #alert{}. +-spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> + {binary(), binary(), connection_state} | #alert{}. %% %% Description: Payload decryption %%-------------------------------------------------------------------- @@ -359,26 +336,7 @@ decipher(Version, CipherFragment, #alert{} = Alert -> Alert end. -%%-------------------------------------------------------------------- --spec decipher_aead(ssl_version(), binary(), connection_state(), binary()) -> - {binary(), binary(), connection_state()} | #alert{}. -%% -%% Description: Payload decryption -%%-------------------------------------------------------------------- -decipher_aead(Version, CipherFragment, - #{sequence_number := SeqNo, - security_parameters := - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo}, - cipher_state := CipherS0 - } = ReadState, AAD) -> - case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, CipherFragment, Version) of - {PlainFragment, CipherS1} -> - CS1 = ReadState#{cipher_state => CipherS1}, - {PlainFragment, CS1}; - #alert{} = Alert -> - Alert - end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 831bbefc59..e3ffbea3d3 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -56,11 +56,11 @@ reinit_handshake_data/1, select_sni_extension/1]). %% Alert and close handling --export([send_alert/2, close/5]). +-export([send_alert/2, close/5, protocol_name/0]). %% Data handling -export([passive_receive/2, next_record_if_active/1, handle_common_event/4, send/3, - socket/5]). + socket/5, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -164,6 +164,8 @@ encode_data(Data, Version, ConnectionStates0)-> encode_alert(#alert{} = Alert, Version, ConnectionStates) -> tls_record:encode_alert_record(Alert, Version, ConnectionStates). +protocol_name() -> + "TLS". %%==================================================================== %% tls_connection_sup API %%==================================================================== @@ -195,6 +197,10 @@ callback_mode() -> socket(Pid, Transport, Socket, Connection, Tracker) -> tls_socket:socket(Pid, Transport, Socket, Connection, Tracker). +setopts(Transport, Socket, Other) -> + tls_socket:setopts(Transport, Socket, Other). +getopts(Transport, Socket, Tag) -> + tls_socket:getopts(Transport, Socket, Tag). %%-------------------------------------------------------------------- %% State functions @@ -220,7 +226,7 @@ init({call, From}, {start, Timeout}, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, - HelloVersion = tls_record:lowest_protocol_version(SslOpts#ssl_options.versions), + HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions), Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0, V2HComp), @@ -596,8 +602,12 @@ next_record(#state{protocol_buffers = next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, socket = Socket, transport_cb = Transport} = State) -> - tls_socket:setopts(Transport, Socket, [{active,once}]), - {no_record, State}; + case tls_socket:setopts(Transport, Socket, [{active,once}]) of + ok -> + {no_record, State}; + _ -> + {socket_closed, State} + end; next_record(State) -> {no_record, State}. @@ -622,10 +632,15 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). +next_event(StateName, socket_closed, State, _) -> + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown, transport_closed}, 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); + {socket_closed, State} -> + next_event(StateName, socket_closed, State, Actions); {#ssl_tls{} = Record, State} -> {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; {#alert{} = Alert, State} -> @@ -706,7 +721,7 @@ close(downgrade, _,_,_,_) -> %% Other close(_, Socket, Transport, _,_) -> Transport:close(Socket). - + convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") -> State#state{ssl_options = convert_options_partial_chain(Options, up)}; convert_state(#state{ssl_options = Options} = State, down, "5.3.6", "5.3.5") -> diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 80d0239498..b54540393a 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -56,7 +56,7 @@ client_hello(Host, Port, ConnectionStates, Version = tls_record:highest_protocol_version(Versions), #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), - Extensions = ssl_handshake:client_hello_extensions(Host, Version, + Extensions = ssl_handshake:client_hello_extensions(Version, AvailableCipherSuites, SslOpts, ConnectionStates, Renegotiation), CipherSuites = @@ -88,7 +88,7 @@ client_hello(Host, Port, ConnectionStates, #hello_extensions{}, {ssl_cipher:hash(), ssl_cipher:sign_algo()} | undefined} | #alert{}. %% -%% Description: Handles a recieved hello message +%% Description: Handles a received hello message %%-------------------------------------------------------------------- hello(#server_hello{server_version = Version, random = Random, cipher_suite = CipherSuite, diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 993a1622fe..4ac6cdc6b5 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -43,7 +43,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]). + is_acceptable_version/1, is_acceptable_version/2, hello_version/2]). %% Decoding -export([decode_cipher_text/3]). @@ -277,6 +277,7 @@ supported_protocol_versions([_|_] = Vsns) -> NewVsns end end. + %%-------------------------------------------------------------------- %% %% Description: ssl version 2 is not acceptable security risks are too big. @@ -296,6 +297,11 @@ 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) -> + lowest_protocol_version(Versions). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -372,7 +378,7 @@ get_tls_records_aux(Data, Acc) -> end. encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) -> - {CipherFragment, Write1} = ssl_record:encode_plain_text(Type, Version, Data, Write0), + {CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0), {CipherText, Write} = encode_tls_cipher_text(Type, Version, CipherFragment, Write1), {CipherText, ConnectionStates#{current_write => Write}}. @@ -446,19 +452,24 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, #{current_read := #{compression_state := CompressionS0, sequence_number := Seq, + cipher_state := CipherS0, security_parameters := #security_parameters{ cipher_type = ?AEAD, + bulk_cipher_algorithm = + BulkCipherAlgo, compression_algorithm = CompAlg} } = ReadState0} = ConnnectionStates0, _) -> - AAD = ssl_cipher:calc_aad(Type, Version, ReadState0), - case ssl_record:decipher_aead(Version, CipherFragment, ReadState0, AAD) of - {PlainFragment, ReadState1} -> + AAD = calc_aad(Type, Version, ReadState0), + case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, CipherFragment, Version) of + {PlainFragment, CipherS1} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), ConnnectionStates = ConnnectionStates0#{ - current_read => ReadState1#{sequence_number => Seq + 1, - compression_state => CompressionS1}}, + current_read => ReadState0#{ + cipher_state => CipherS1, + sequence_number => Seq + 1, + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; #alert{} = Alert -> Alert @@ -489,4 +500,29 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, end; #alert{} = Alert -> Alert - end. + end. + +do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + } = WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + AAD = calc_aad(Type, Version, WriteState1), + ssl_record:cipher_aead(Version, Comp, WriteState1, AAD); +do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + }= WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1), + ssl_record:cipher(Version, Comp, WriteState1, MacHash); +do_encode_plain_text(_,_,_,CS) -> + exit({cs, CS}). + +calc_aad(Type, {MajVer, MinVer}, + #{sequence_number := SeqNo}) -> + <<?UINT64(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 6797b290d4..a8fe119bf8 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -29,7 +29,7 @@ -include("ssl_internal.hrl"). -include("ssl_record.hrl"). --export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, +-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3, setup_keys/8, suites/1, prf/5, ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, default_signature_algs/1, signature_algs/2]). @@ -221,11 +221,7 @@ suites(Minor) when Minor == 1; Minor == 2 -> ?TLS_RSA_WITH_3DES_EDE_CBC_SHA ]; suites(3) -> - [ - ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - - ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, @@ -234,7 +230,10 @@ suites(3) -> ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, @@ -407,7 +406,7 @@ is_pair(Hash, rsa, Hashs) -> AtLeastMd5 = Hashs -- [md2,md4], lists:member(Hash, AtLeastMd5). -%% list ECC curves in prefered order +%% list ECC curves in preferred order -spec ecc_curves(1..3 | all) -> [named_curve()]. ecc_curves(all) -> [sect571r1,sect571k1,secp521r1,brainpoolP512r1, diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index a2eb4ce449..558be6d642 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2016. All Rights Reserved. +# Copyright Ericsson AB 1999-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -56,7 +56,8 @@ MODULES = \ ssl_upgrade_SUITE\ ssl_sni_SUITE \ make_certs\ - erl_make_certs + erl_make_certs\ + x509_test ERL_FILES = $(MODULES:%=%.erl) diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl index a6657be995..3ab6222780 100644 --- a/lib/ssl/test/erl_make_certs.erl +++ b/lib/ssl/test/erl_make_certs.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -179,7 +179,7 @@ make_tbs(SubjectKey, Opts) -> subject(proplists:get_value(subject, Opts),false) end, - {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + {#'OTPTBSCertificate'{serialNumber = trunc(rand:uniform()*100000000)*10000 + 1, signature = SignAlgo, issuer = Issuer, validity = validity(Opts), diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index e14f7f60c4..ecbacc1590 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -385,6 +385,7 @@ req_cnf(Root, C) -> "subjectAltName = email:copy\n"]. ca_cnf(Root, C = #config{issuing_distribution_point = true}) -> + Hostname = net_adm:localhost(), ["# Purpose: Configuration for CAs.\n" "\n" "ROOTDIR = " ++ Root ++ "\n" @@ -434,7 +435,7 @@ ca_cnf(Root, C = #config{issuing_distribution_point = true}) -> "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid,issuer:always\n" - "subjectAltName = email:copy\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" "issuerAltName = issuer:copy\n" "crlDistributionPoints=@crl_section\n" @@ -449,7 +450,7 @@ ca_cnf(Root, C = #config{issuing_distribution_point = true}) -> "keyUsage = digitalSignature\n" "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid,issuer:always\n" - "subjectAltName = email:copy\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" "issuerAltName = issuer:copy\n" "\n" @@ -458,12 +459,13 @@ ca_cnf(Root, C = #config{issuing_distribution_point = true}) -> "keyUsage = cRLSign, keyCertSign\n" "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid:always,issuer:always\n" - "subjectAltName = email:copy\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" "issuerAltName = issuer:copy\n" "crlDistributionPoints=@crl_section\n" ]; ca_cnf(Root, C = #config{issuing_distribution_point = false}) -> + Hostname = net_adm:localhost(), ["# Purpose: Configuration for CAs.\n" "\n" "ROOTDIR = " ++ Root ++ "\n" @@ -513,7 +515,7 @@ ca_cnf(Root, C = #config{issuing_distribution_point = false}) -> "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid,issuer:always\n" - "subjectAltName = email:copy\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" "issuerAltName = issuer:copy\n" %"crlDistributionPoints=@crl_section\n" @@ -528,7 +530,7 @@ ca_cnf(Root, C = #config{issuing_distribution_point = false}) -> "keyUsage = digitalSignature\n" "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid,issuer:always\n" - "subjectAltName = email:copy\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" "issuerAltName = issuer:copy\n" "\n" diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index f779765b18..0fbb0bb79a 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -44,26 +44,74 @@ groups() -> {'tlsv1.2', [], all_versions_groups()}, {'tlsv1.1', [], all_versions_groups()}, {'tlsv1', [], all_versions_groups()}, - {'erlang_server', [], key_cert_combinations()}, - {'erlang_client', [], key_cert_combinations()}, - {'erlang', [], key_cert_combinations() ++ misc() ++ ecc_negotiation()} + {'erlang_server', [], openssl_key_cert_combinations()}, + %%{'erlang_client', [], openssl_key_cert_combinations()}, + {'erlang', [], key_cert_combinations() ++ misc() + ++ ecc_negotiation()} ]. all_versions_groups ()-> [{group, 'erlang_server'}, - {group, 'erlang_client'}, + %%{group, 'erlang_client'}, {group, 'erlang'} ]. + +openssl_key_cert_combinations() -> + ECDH_RSA = case ssl_test_lib:openssl_filter("ECDH-RSA") of + [] -> + []; + _ -> + server_ecdh_rsa() + end, + + ECDHE_RSA = case ssl_test_lib:openssl_filter("ECDHE-RSA") of + [] -> + []; + _ -> + server_ecdhe_rsa() + end, + ECDH_ECDSA = case ssl_test_lib:openssl_filter("ECDH-ECDSA") of + [] -> + []; + _ -> + server_ecdhe_ecdsa() + end, + + ECDHE_ECDSA = case ssl_test_lib:openssl_filter("ECDHE-ECDSA") of + [] -> + []; + _ -> + server_ecdhe_ecdsa() + end, + ECDH_RSA ++ ECDHE_RSA ++ ECDH_ECDSA ++ ECDHE_ECDSA. + key_cert_combinations() -> - [client_ecdh_server_ecdh, - client_rsa_server_ecdh, - client_ecdh_server_rsa, - client_rsa_server_rsa, - client_ecdsa_server_ecdsa, - client_ecdsa_server_rsa, - client_rsa_server_ecdsa - ]. + server_ecdh_rsa() ++ + server_ecdhe_rsa() ++ + server_ecdh_ecdsa() ++ + server_ecdhe_ecdsa(). + +server_ecdh_rsa() -> + [client_ecdh_rsa_server_ecdh_rsa, + client_ecdhe_rsa_server_ecdh_rsa, + client_ecdhe_ecdsa_server_ecdh_rsa]. + +server_ecdhe_rsa() -> + [client_ecdh_rsa_server_ecdhe_rsa, + client_ecdhe_rsa_server_ecdhe_rsa, + client_ecdhe_ecdsa_server_ecdhe_rsa]. + +server_ecdh_ecdsa() -> + [client_ecdh_ecdsa_server_ecdh_ecdsa, + client_ecdhe_rsa_server_ecdh_ecdsa, + client_ecdhe_ecdsa_server_ecdh_ecdsa]. + +server_ecdhe_ecdsa() -> + [client_ecdh_rsa_server_ecdhe_ecdsa, + client_ecdh_ecdsa_server_ecdhe_ecdsa, + client_ecdhe_ecdsa_server_ecdhe_ecdsa]. + misc()-> [client_ecdsa_server_ecdsa_with_raw_key]. @@ -74,15 +122,15 @@ ecc_negotiation() -> ecc_client_order, ecc_client_order_custom_curves, ecc_unknown_curve, - client_ecdh_server_ecdh_ecc_server_custom, - client_rsa_server_ecdh_ecc_server_custom, - client_ecdh_server_rsa_ecc_server_custom, - client_rsa_server_rsa_ecc_server_custom, - client_ecdsa_server_ecdsa_ecc_server_custom, - client_ecdsa_server_rsa_ecc_server_custom, - client_rsa_server_ecdsa_ecc_server_custom, - client_ecdsa_server_ecdsa_ecc_client_custom, - client_rsa_server_ecdsa_ecc_client_custom + client_ecdh_rsa_server_ecdhe_ecdsa_server_custom, + client_ecdh_rsa_server_ecdhe_rsa_server_custom, + client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom, + client_ecdhe_rsa_server_ecdhe_rsa_server_custom, + client_ecdhe_rsa_server_ecdh_rsa_server_custom, + client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom, + client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom, + client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom, + client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom ]. %%-------------------------------------------------------------------- @@ -90,12 +138,7 @@ init_per_suite(Config0) -> end_per_suite(Config0), try crypto:start() of ok -> - %% make rsa certs using oppenssl - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), - proplists:get_value(priv_dir, Config0)), - Config1 = ssl_test_lib:make_ecdsa_cert(Config0), - Config2 = ssl_test_lib:make_ecdh_rsa_cert(Config1), - ssl_test_lib:cert_options(Config2) + Config0 catch _:_ -> {skip, "Crypto did not start"} end. @@ -174,70 +217,84 @@ end_per_testcase(_TestCase, Config) -> %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -client_ecdh_server_ecdh(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), - basic_test(COpts, SOpts, Config). - -client_ecdh_server_rsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_opts, Config), - basic_test(COpts, SOpts, Config). - -client_rsa_server_ecdh(Config) when is_list(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), - basic_test(COpts, SOpts, Config). +%% Test diffrent certificate chain types, note that it is the servers +%% chain that affect what cipher suit that will be choosen + +%% ECDH_RSA +client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], + ecdh_rsa, ecdh_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_rsa, ecdh_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdh_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). + +%% ECDHE_RSA +client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdh_rsa, ecdhe_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_rsa, ecdhe_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdh_ecdsa, ecdhe_rsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). -client_rsa_server_rsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_opts, Config), - basic_test(COpts, SOpts, Config). - -client_ecdsa_server_ecdsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), - basic_test(COpts, SOpts, Config). - -client_ecdsa_server_rsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_opts, Config), - basic_test(COpts, SOpts, Config). - -client_rsa_server_ecdsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), - basic_test(COpts, SOpts, Config). +%% ECDH_ECDSA +client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_peer_opts, + [{extensions, [{key_usage, [keyEncipherment] + }]}]}], + ecdh_ecdsa, ecdh_ecdsa, Config), + basic_test(COpts, SOpts, + [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_peer_opts, + [{extensions, [{key_usage, [keyEncipherment] + }]}]}], + ecdhe_rsa, ecdh_ecdsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). + +client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_peer_opts, + [{extensions, [{key_usage, [keyEncipherment] + }]}]}], + ecdhe_ecdsa, ecdh_ecdsa, Config), + basic_test(COpts, SOpts, + [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). + +%% ECDHE_ECDSA +client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdh_rsa, ecdhe_ecdsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdh_ecdsa, ecdhe_ecdsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_ecdsa, ecdhe_ecdsa, Config), + basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), - ServerCert = proplists:get_value(certfile, SOpts), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_ecdsa, ecdhe_ecdsa, Config), ServerKeyFile = proplists:get_value(keyfile, SOpts), {ok, PemBin} = file:read_file(ServerKeyFile), PemEntries = public_key:pem_decode(PemBin), {'ECPrivateKey', Key, not_encrypted} = proplists:lookup('ECPrivateKey', PemEntries), ServerKey = {'ECPrivateKey', Key}, - ServerCA = proplists:get_value(cacertfile, SOpts), - ClientCert = proplists:get_value(certfile, COpts), - ClientKey = proplists:get_value(keyfile, COpts), - ClientCA = proplists:get_value(cacertfile, COpts), SType = proplists:get_value(server_type, Config), CType = proplists:get_value(client_type, Config), {Server, Port} = start_server_with_raw_key(SType, - ClientCA, ServerCA, - ServerCert, - ServerKey, - Config), - Client = start_client(CType, Port, ServerCA, ClientCA, - ClientCert, - ClientKey, Config), + [{key, ServerKey} | proplists:delete(keyfile, SOpts)], + Config), + Client = start_client(CType, Port, COpts, Config), check_result(Server, SType, Client, CType), close(Server, Client). ecc_default_order(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [], case supported_eccs([{eccs, [sect571r1]}]) of true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); @@ -245,8 +302,7 @@ ecc_default_order(Config) -> end. ecc_default_order_custom_curves(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); @@ -254,8 +310,7 @@ ecc_default_order_custom_curves(Config) -> end. ecc_client_order(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{honor_ecc_order, false}], case supported_eccs([{eccs, [sect571r1]}]) of true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); @@ -263,8 +318,7 @@ ecc_client_order(Config) -> end. ecc_client_order_custom_curves(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); @@ -272,89 +326,77 @@ ecc_client_order_custom_curves(Config) -> end. ecc_unknown_curve(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{eccs, ['123_fake_curve']}], ecc_test_error(COpts, SOpts, [], ECCOpts, Config). -%% We can only expect to see a named curve on a conn with -%% a server supporting ecdsa. Otherwise the curve is selected -%% but not used and communicated to the client? -client_ecdh_server_ecdh_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), +client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdh_rsa, ecdhe_ecdsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_ecdh_server_rsa_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_opts, Config), +client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdh_rsa, ecdhe_rsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_rsa_server_ecdh_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), +client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_rsa, ecdhe_ecdsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_rsa_server_rsa_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_opts, Config), +client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_rsa, ecdhe_rsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. - -client_ecdsa_server_ecdsa_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), +client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_peer_opts, + [{extensions, [{key_usage, [keyEncipherment] + }]}]}], ecdhe_rsa, ecdh_rsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_ecdsa_server_rsa_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_opts, Config), +client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([], ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_rsa_server_ecdsa_ecc_server_custom(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), +client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_rsa, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. -client_ecdsa_server_ecdsa_ecc_client_custom(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), +client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_ecdsa, ecdhe_ecdsa, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); false -> {skip, "unsupported named curves"} end. -client_rsa_server_ecdsa_ecc_client_custom(Config) -> - COpts = proplists:get_value(client_opts, Config), - SOpts = proplists:get_value(server_ecdsa_opts, Config), +client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom(Config) -> + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([],ecdhe_rsa, ecdhe_ecdsa, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], case supported_eccs(ECCOpts) of true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); @@ -365,57 +407,31 @@ client_rsa_server_ecdsa_ecc_client_custom(Config) -> %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- basic_test(COpts, SOpts, Config) -> - basic_test(proplists:get_value(certfile, COpts), - proplists:get_value(keyfile, COpts), - proplists:get_value(cacertfile, COpts), - proplists:get_value(certfile, SOpts), - proplists:get_value(keyfile, SOpts), - proplists:get_value(cacertfile, SOpts), - Config). - -basic_test(ClientCert, ClientKey, ClientCA, ServerCert, ServerKey, ServerCA, Config) -> SType = proplists:get_value(server_type, Config), CType = proplists:get_value(client_type, Config), - {Server, Port} = start_server(SType, - ClientCA, ServerCA, - ServerCert, - ServerKey, - Config), - Client = start_client(CType, Port, ServerCA, ClientCA, - ClientCert, - ClientKey, Config), + {Server, Port} = start_server(SType, SOpts, Config), + Client = start_client(CType, Port, COpts, Config), check_result(Server, SType, Client, CType), close(Server, Client). ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> - CCA = proplists:get_value(cacertfile, COpts), - CCert = proplists:get_value(certfile, COpts), - CKey = proplists:get_value(keyfile, COpts), - SCA = proplists:get_value(cacertfile, SOpts), - SCert = proplists:get_value(certfile, SOpts), - SKey = proplists:get_value(keyfile, SOpts), - {Server, Port} = start_server_ecc(erlang, CCA, SCA, SCert, SKey, Expect, SECCOpts, Config), - Client = start_client_ecc(erlang, Port, SCA, CCA, CCert, CKey, Expect, CECCOpts, Config), + {Server, Port} = start_server_ecc(erlang, SOpts, Expect, SECCOpts, Config), + Client = start_client_ecc(erlang, Port, COpts, Expect, CECCOpts, Config), ssl_test_lib:check_result(Server, ok, Client, ok), close(Server, Client). ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> - CCA = proplists:get_value(cacertfile, COpts), - CCert = proplists:get_value(certfile, COpts), - CKey = proplists:get_value(keyfile, COpts), - SCA = proplists:get_value(cacertfile, SOpts), - SCert = proplists:get_value(certfile, SOpts), - SKey = proplists:get_value(keyfile, SOpts), - {Server, Port} = start_server_ecc_error(erlang, CCA, SCA, SCert, SKey, SECCOpts, Config), - Client = start_client_ecc_error(erlang, Port, SCA, CCA, CCert, CKey, CECCOpts, Config), + {Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config), + Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), Error = {error, {tls_alert, "insufficient security"}}, ssl_test_lib:check_result(Server, Error, Client, Error). -start_client(openssl, Port, PeerCA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - CA = new_openssl_ca(filename:join(PrivDir, "openssl_client_ca.pem"), PeerCA, OwnCa), +start_client(openssl, Port, ClientOpts, _Config) -> + Cert = proplists:get_value(certfile, ClientOpts), + Key = proplists:get_value(keyfile, ClientOpts), + CA = proplists:get_value(cacertfile, ClientOpts), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), @@ -426,21 +442,18 @@ start_client(openssl, Port, PeerCA, OwnCa, Cert, Key, Config) -> OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), OpenSslPort; -start_client(erlang, Port, PeerCA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - CA = new_ca(filename:join(PrivDir,"erlang_client_ca.pem"), PeerCA, OwnCa), + +start_client(erlang, Port, ClientOpts, Config) -> {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + KeyEx = proplists:get_value(check_keyex, Config, false), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{verify, verify_peer}, - {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]). + {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, + {options, [{verify, verify_peer} | ClientOpts]}]). -start_client_ecc(erlang, Port, PeerCA, OwnCa, Cert, Key, Expect, ECCOpts, Config) -> - CA = new_ca("erlang_client_ca", PeerCA, OwnCa), +start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) -> {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -448,26 +461,22 @@ start_client_ecc(erlang, Port, PeerCA, OwnCa, Cert, Key, Expect, ECCOpts, Config {mfa, {?MODULE, check_ecc, [client, Expect]}}, {options, ECCOpts ++ - [{verify, verify_peer}, - {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]). + [{verify, verify_peer} | ClientOpts]}]). -start_client_ecc_error(erlang, Port, PeerCA, OwnCa, Cert, Key, ECCOpts, Config) -> - CA = new_ca("erlang_client_ca", PeerCA, OwnCa), +start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) -> {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {options, ECCOpts ++ - [{verify, verify_peer}, - {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]). + [{verify, verify_peer} | ClientOpts]}]). -start_server(openssl, PeerCA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - CA = new_openssl_ca(filename:join(PrivDir,"openssl_server_ca.pem"), PeerCA, OwnCa), +start_server(openssl, ServerOpts, _Config) -> + Cert = proplists:get_value(certfile, ServerOpts), + Key = proplists:get_value(keyfile, ServerOpts), + CA = proplists:get_value(cacertfile, ServerOpts), Port = ssl_test_lib:inet_port(node()), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", @@ -477,23 +486,18 @@ start_server(openssl, PeerCA, OwnCa, Cert, Key, Config) -> OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), {OpenSslPort, Port}; -start_server(erlang, PeerCA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - CA = new_ca(filename:join(PrivDir,"erlang_server_ca.pem"), PeerCA, OwnCa), +start_server(erlang, ServerOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), + KeyEx = proplists:get_value(check_keyex, Config, false), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, - []}}, - {options, - [{verify, verify_peer}, {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]), + {from, self()}, + {mfa, {ssl_test_lib, + check_key_exchange_send_active, + [KeyEx]}}, + {options, [{verify, verify_peer} | ServerOpts]}]), {Server, ssl_test_lib:inet_port(Server)}. -start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - CA = new_ca(filename:join(PrivDir, "erlang_server_ca.pem"), PeerCA, OwnCa), +start_server_with_raw_key(erlang, ServerOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -501,31 +505,26 @@ start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> send_recv_result_active, []}}, {options, - [{verify, verify_peer}, {cacertfile, CA}, - {certfile, Cert}, {key, Key}]}]), + [{verify, verify_peer} | ServerOpts]}]), {Server, ssl_test_lib:inet_port(Server)}. -start_server_ecc(erlang, PeerCA, OwnCa, Cert, Key, Expect, ECCOpts, Config) -> - CA = new_ca("erlang_server_ca", PeerCA, OwnCa), +start_server_ecc(erlang, ServerOpts, Expect, ECCOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, check_ecc, [server, Expect]}}, {options, ECCOpts ++ - [{verify, verify_peer}, {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]), + [{verify, verify_peer} | ServerOpts]}]), {Server, ssl_test_lib:inet_port(Server)}. -start_server_ecc_error(erlang, PeerCA, OwnCa, Cert, Key, ECCOpts, Config) -> - CA = new_ca("erlang_server_ca", PeerCA, OwnCa), +start_server_ecc_error(erlang, ServerOpts, ECCOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, ECCOpts ++ - [{verify, verify_peer}, {cacertfile, CA}, - {certfile, Cert}, {keyfile, Key}]}]), + [{verify, verify_peer} | ServerOpts]}]), {Server, ssl_test_lib:inet_port(Server)}. check_result(Server, erlang, Client, erlang) -> @@ -561,24 +560,6 @@ close(Client, Server) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -new_ca(FileName, CA, OwnCa) -> - {ok, P1} = file:read_file(CA), - E1 = public_key:pem_decode(P1), - {ok, P2} = file:read_file(OwnCa), - E2 = public_key:pem_decode(P2), - Pem = public_key:pem_encode(E1 ++E2), - file:write_file(FileName, Pem), - FileName. - -new_openssl_ca(FileName, CA, OwnCa) -> - {ok, P1} = file:read_file(CA), - E1 = public_key:pem_decode(P1), - {ok, P2} = file:read_file(OwnCa), - E2 = public_key:pem_decode(P2), - Pem = public_key:pem_encode(E2 ++E1), - file:write_file(FileName, Pem), - FileName. - supported_eccs(Opts) -> ToCheck = proplists:get_value(eccs, Opts, []), Supported = ssl:eccs(), diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index c846913598..8a4d827456 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -119,7 +119,6 @@ options_tests() -> [der_input, ssl_options_not_proplist, raw_ssl_option, - socket_options, invalid_inet_get_option, invalid_inet_get_option_not_list, invalid_inet_get_option_improper_list, @@ -148,6 +147,7 @@ options_tests_tls() -> api_tests() -> [connection_info, + secret_connection_info, connection_information, peercert, peercert_with_client_cert, @@ -162,7 +162,8 @@ api_tests() -> ssl_recv_timeout, server_name_indication_option, accept_pool, - prf + prf, + socket_options ]. api_tests_tls() -> @@ -177,6 +178,7 @@ api_tests_tls() -> tls_shutdown_error, peername, sockname, + tls_socket_options, new_options_in_accept ]. @@ -239,6 +241,7 @@ error_handling_tests()-> error_handling_tests_tls()-> [controller_dies, tls_client_closes_socket, + tls_closed_in_active_once, tls_tcp_error_propagation_in_active_mode, tls_tcp_connect, tls_tcp_connect_big, @@ -357,6 +360,8 @@ init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; TestCase == psk_with_hint_cipher_suites; TestCase == ciphers_rsa_signed_certs; TestCase == ciphers_rsa_signed_certs_openssl_names; + TestCase == ciphers_ecdh_rsa_signed_certs_openssl_names; + TestCase == ciphers_ecdh_rsa_signed_certs; TestCase == ciphers_dsa_signed_certs; TestCase == ciphers_dsa_signed_certs_openssl_names; TestCase == anonymous_cipher_suites; @@ -383,22 +388,27 @@ init_per_testcase(reuse_session, Config) -> init_per_testcase(rizzo, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 40}), + ct:timetrap({seconds, 60}), + Config; + +init_per_testcase(no_rizzo_rc4, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 60}), Config; init_per_testcase(rizzo_one_n_minus_one, Config) -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), + ct:timetrap({seconds, 60}), rizzo_add_mitigation_option(one_n_minus_one, Config); init_per_testcase(rizzo_zero_n, Config) -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), + ct:timetrap({seconds, 60}), rizzo_add_mitigation_option(zero_n, Config); init_per_testcase(rizzo_disabled, Config) -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), + ct:timetrap({seconds, 60}), rizzo_add_mitigation_option(disabled, Config); init_per_testcase(prf, Config) -> @@ -429,6 +439,7 @@ init_per_testcase(prf, Config) -> init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; TestCase == tls_client_closes_socket; + TestCase == tls_closed_in_active_once; TestCase == tls_downgrade -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 15}), @@ -611,7 +622,7 @@ prf(Config) when is_list(Config) -> %%-------------------------------------------------------------------- connection_info() -> - [{doc,"Test the API function ssl:connection_information/1"}]. + [{doc,"Test the API function ssl:connection_information/2"}]. connection_info(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), @@ -645,6 +656,38 @@ connection_info(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +secret_connection_info() -> + [{doc,"Test the API function ssl:connection_information/2"}]. +secret_connection_info(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + Version = ssl_test_lib:protocol_version(Config), + + ssl_test_lib:check_result(Server, true, Client, true), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- + connection_information() -> [{doc,"Test the API function ssl:connection_information/1"}]. connection_information(Config) when is_list(Config) -> @@ -928,6 +971,48 @@ tls_client_closes_socket(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, {error,closed}). %%-------------------------------------------------------------------- +tls_closed_in_active_once() -> + [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. + +tls_closed_in_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + Port = ssl_test_lib:inet_port(node()), + Server = fun() -> + {ok, Listen} = gen_tcp:listen(Port, TcpOpts), + {ok, TcpServerSocket} = gen_tcp:accept(Listen), + {ok, ServerSocket} = ssl:ssl_accept(TcpServerSocket, ServerOpts), + lists:foreach( + fun(_) -> + ssl:send(ServerSocket, "some random message\r\n") + end, lists:seq(1, 20)), + %% Close TCP instead of SSL socket to trigger the bug: + gen_tcp:close(TcpServerSocket), + gen_tcp:close(Listen) + end, + spawn_link(Server), + {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), + Result = tls_closed_in_active_once_loop(Socket), + ssl:close(Socket), + case Result of + ok -> ok; + _ -> ct:fail(Result) + end. + +tls_closed_in_active_once_loop(Socket) -> + ssl:setopts(Socket, [{active, once}]), + receive + {ssl, Socket, _} -> + tls_closed_in_active_once_loop(Socket); + {ssl_closed, Socket} -> + ok + after 5000 -> + no_ssl_closed_received + end. + +%%-------------------------------------------------------------------- connect_dist() -> [{doc,"Test a simple connect as is used by distribution"}]. @@ -1209,10 +1294,10 @@ cipher_suites_mix(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -socket_options() -> +tls_socket_options() -> [{doc,"Test API function getopts/2 and setopts/2"}]. -socket_options(Config) when is_list(Config) -> +tls_socket_options(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1227,14 +1312,14 @@ socket_options(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, socket_options_result, + {mfa, {?MODULE, tls_socket_options_result, [Options, Values, NewOptions, NewValues]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, socket_options_result, + {mfa, {?MODULE, tls_socket_options_result, [Options, Values, NewOptions, NewValues]}}, {options, ClientOpts}]), @@ -1249,7 +1334,7 @@ socket_options(Config) when is_list(Config) -> {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), ssl:close(Listen). -socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> +tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> %% Test get/set emulated opts {ok, DefaultValues} = ssl:getopts(Socket, Options), ssl:setopts(Socket, NewValues), @@ -1264,6 +1349,59 @@ socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> %%-------------------------------------------------------------------- +socket_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +socket_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {active, true}], + %% Shall be the reverse order of Values! + Options = [active, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). + + +socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + + +%%-------------------------------------------------------------------- invalid_inet_get_option() -> [{doc,"Test handling of invalid inet options in getopts"}]. @@ -2847,10 +2985,10 @@ der_input(Config) when is_list(Config) -> Size = ets:info(CADb, size), - SeverVerifyOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + SeverVerifyOpts = ssl_test_lib:ssl_options(server_opts, Config), {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | SeverVerifyOpts]), - ClientVerifyOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientVerifyOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | ClientVerifyOpts]), ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, @@ -3414,7 +3552,6 @@ listen_socket(Config) -> {error, enotconn} = ssl:connection_information(ListenSocket), {error, enotconn} = ssl:peername(ListenSocket), {error, enotconn} = ssl:peercert(ListenSocket), - {error, enotconn} = ssl:session_info(ListenSocket), {error, enotconn} = ssl:renegotiate(ListenSocket), {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, client_random, 256), {error, enotconn} = ssl:shutdown(ListenSocket, read_write), @@ -4040,11 +4177,11 @@ prf_create_plan(TlsVersions, PRFs, Results) -> prf_ciphers_and_expected(TlsVer, PRFs, Results) -> case TlsVer of TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 - orelse TlsVer == 'tlsv1.1' -> + orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> Ciphers = ssl:cipher_suites(), {_, Expected} = lists:keyfind(md5sha, 1, Results), [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; - 'tlsv1.2' -> + TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> lists:foldl( fun(PRF, Acc) -> Ciphers = prf_get_ciphers(TlsVer, PRF), @@ -4059,21 +4196,20 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) -> end end, [], PRFs) end. -prf_get_ciphers(TlsVer, PRF) -> - case TlsVer of - 'tlsv1.2' -> - lists:filter( - fun(C) when tuple_size(C) == 4 andalso - element(4, C) == PRF -> - true; - (_) -> false - end, ssl:cipher_suites()) - end. +prf_get_ciphers(_, PRF) -> + lists:filter( + fun(C) when tuple_size(C) == 4 andalso + element(4, C) == PRF -> + true; + (_) -> + false + end, + ssl:cipher_suites()). prf_run_test(_, TlsVer, [], _, Prf) -> ct:fail({error, cipher_list_empty, TlsVer, Prf}); prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}], + BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), Server = ssl_test_lib:start_server( @@ -4639,6 +4775,11 @@ version_info_result(Socket) -> {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. +secret_connection_info_result(Socket) -> + {ok, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]} + = ssl:connection_information(Socket, [client_random, server_random, master_secret]), + is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret). + connect_dist_s(S) -> Msg = term_to_binary({erlang,term}), ok = ssl:send(S, Msg). @@ -4771,3 +4912,9 @@ wait_for_send(Socket) -> %% Make sure TLS process processed send message event _ = ssl:connection_information(Socket). +tls_or_dtls('dtlsv1') -> + dtls; +tls_or_dtls('dtlsv1.2') -> + dtls; +tls_or_dtls(_) -> + tls. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index 21989f8d99..ae2928b1c3 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2014-2016. All Rights Reserved. +%% Copyright Ericsson AB 2014-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -88,7 +88,6 @@ end_per_testcase(_Func, _Conf) -> -define(FPROF_SERVER, false). -define(EPROF_CLIENT, false). -define(EPROF_SERVER, false). --define(PERCEPT_SERVER, false). %% Current numbers gives roughly a testcase per minute on todays hardware.. @@ -190,7 +189,6 @@ server_init(ssl, setup_connection, _, _, Server) -> ?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]), %%?EPROF_SERVER andalso start_profile(eprof, [ssl_connection_sup, ssl_manager]), ?EPROF_SERVER andalso start_profile(eprof, [ssl_manager]), - ?PERCEPT_SERVER andalso percept:profile("/tmp/ssl_server.percept"), Server ! {self(), {init, Host, Port}}, Test = fun(TSocket) -> ok = ssl:ssl_accept(TSocket), @@ -247,7 +245,6 @@ setup_server_connection(LSocket, Test) -> receive quit -> ?FPROF_SERVER andalso stop_profile(fprof, "test_server_res.fprof"), ?EPROF_SERVER andalso stop_profile(eprof, "test_server_res.eprof"), - ?PERCEPT_SERVER andalso stop_profile(percept, "/tmp/ssl_server.percept"), ok after 0 -> case ssl:transport_accept(LSocket, 2000) of @@ -388,13 +385,6 @@ start_profile(fprof, Procs) -> fprof:trace([start, {procs, Procs}]), io:format("(F)Profiling ...",[]). -stop_profile(percept, File) -> - percept:stop_profile(), - percept:analyze(File), - {started, _Host, Port} = percept:start_webserver(), - wx:new(), - wx_misc:launchDefaultBrowser("http://" ++ net_adm:localhost() ++ ":" ++ integer_to_list(Port)), - ok; stop_profile(eprof, File) -> profiling_stopped = eprof:stop_profiling(), eprof:log(File), @@ -420,13 +410,19 @@ ssl_opts(connect_der) -> [{verify, verify_peer} | ssl_opts("client_der")]; ssl_opts(Role) -> CertData = cert_data(Role), - [{active, false}, - {depth, 2}, - {reuseaddr, true}, - {mode,binary}, - {nodelay, true}, - {ciphers, [{dhe_rsa,aes_256_cbc,sha}]} - |CertData]. + Opts = [{active, false}, + {depth, 2}, + {reuseaddr, true}, + {mode,binary}, + {nodelay, true}, + {ciphers, [{dhe_rsa,aes_256_cbc,sha}]} + |CertData], + case Role of + "client" ++ _ -> + [{server_name_indication, disable} | Opts]; + "server" ++ _ -> + Opts + end. cert_data(Der) when Der =:= "server_der"; Der =:= "client_der" -> [Role,_] = string:tokens(Der, "_"), diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index bed6f87c74..6221cffdc1 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -74,7 +74,7 @@ tests() -> cert_expired, invalid_signature_client, invalid_signature_server, - extended_key_usage_verify_client, + extended_key_usage_verify_both, extended_key_usage_verify_server, critical_extension_verify_client, critical_extension_verify_server, @@ -88,18 +88,14 @@ error_handling_tests()-> unknown_server_ca_accept_verify_peer, unknown_server_ca_accept_backwardscompatibility, no_authority_key_identifier, - no_authority_key_identifier_and_nonstandard_encoding]. + no_authority_key_identifier_keyEncipherment]. -init_per_suite(Config0) -> +init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl_test_lib:clean_start(), - %% make rsa certs using oppenssl - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), - proplists:get_value(priv_dir, Config0)), - Config = ssl_test_lib:make_dsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -108,54 +104,44 @@ end_per_suite(_Config) -> ssl:stop(), application:stop(crypto). -init_per_group(tls, Config) -> +init_per_group(tls, Config0) -> Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), ssl:stop(), application:load(ssl), application:set_env(ssl, protocol_version, Version), - application:set_env(ssl, bypass_pem_cache, Version), ssl:start(), - NewConfig = proplists:delete(protocol, Config), - [{protocol, tls}, {version, tls_record:protocol_version(Version)} | NewConfig]; + Config = proplists:delete(protocol, Config0), + [{protocol, tls}, {version, tls_record:protocol_version(Version)} | Config]; -init_per_group(dtls, Config) -> +init_per_group(dtls, Config0) -> Version = dtls_record:protocol_version(dtls_record:highest_protocol_version([])), ssl:stop(), application:load(ssl), application:set_env(ssl, protocol_version, Version), - application:set_env(ssl, bypass_pem_cache, Version), ssl:start(), - NewConfig = proplists:delete(protocol_opts, proplists:delete(protocol, Config)), - [{protocol, dtls}, {protocol_opts, [{protocol, dtls}]}, {version, dtls_record:protocol_version(Version)} | NewConfig]; + Config = proplists:delete(protocol_opts, proplists:delete(protocol, Config0)), + [{protocol, dtls}, {protocol_opts, [{protocol, dtls}]}, {version, dtls_record:protocol_version(Version)} | Config]; init_per_group(active, Config) -> - [{active, true}, {receive_function, send_recv_result_active} | Config]; + [{active, true}, {receive_function, send_recv_result_active} | Config]; init_per_group(active_once, Config) -> - [{active, once}, {receive_function, send_recv_result_active_once} | Config]; + [{active, once}, {receive_function, send_recv_result_active_once} | Config]; init_per_group(passive, Config) -> - [{active, false}, {receive_function, send_recv_result} | Config]; + [{active, false}, {receive_function, send_recv_result} | Config]; +init_per_group(error_handling, Config) -> + [{active, false}, {receive_function, send_recv_result} | Config]; + init_per_group(_, Config) -> Config. end_per_group(_GroupName, Config) -> Config. -init_per_testcase(TestCase, Config) when TestCase == cert_expired; - TestCase == invalid_signature_client; - TestCase == invalid_signature_server; - TestCase == extended_key_usage_verify_none; - TestCase == extended_key_usage_verify_peer; - TestCase == critical_extension_verify_none; - TestCase == critical_extension_verify_peer; - TestCase == no_authority_key_identifier; - TestCase == no_authority_key_identifier_and_nonstandard_encoding-> - ssl:clear_pem_cache(), - init_per_testcase(common, Config); init_per_testcase(_TestCase, Config) -> ssl:stop(), ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 5}), + ct:timetrap({seconds, 10}), Config. end_per_testcase(_TestCase, Config) -> @@ -168,23 +154,23 @@ end_per_testcase(_TestCase, Config) -> verify_peer() -> [{doc,"Test option verify_peer"}]. verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} - | ServerOpts]}]), + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, {verify, verify_peer} + | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, {verify, verify_peer} | ClientOpts]}]), + ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). @@ -194,23 +180,24 @@ verify_none() -> [{doc,"Test option verify_none"}]. verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_none} - | ServerOpts]}]), + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, {verify, verify_none} + | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, + {verify, verify_none} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), @@ -222,8 +209,8 @@ server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. server_verify_client_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, []), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, []), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -239,7 +226,7 @@ server_verify_client_once(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), + {options, [{active, Active} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client0, ok), Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, @@ -261,8 +248,8 @@ server_require_peer_cert_ok() -> server_require_peer_cert_ok(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -290,20 +277,21 @@ server_require_peer_cert_fail() -> server_require_peer_cert_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], BadClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), + Active = proplists:get_value(active, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, - {options, [{active, false} | ServerOpts]}]), + {options, [{active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {options, [{active, false} | BadClientOpts]}]), + {options, [{active, Active} | BadClientOpts]}]), receive {Server, {error, {tls_alert, "handshake failure"}}} -> receive @@ -321,24 +309,25 @@ server_require_peer_cert_partial_chain() -> server_require_peer_cert_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + Active = proplists:get_value(active, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,RootCA,_}, {_, _, _}] = public_key:pem_decode(ClientCAs), + [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ServerOpts]}]), + {options, [{active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}, + {options, [{active, Active}, {cacerts, [RootCA]} | proplists:delete(cacertfile, ClientOpts)]}]), receive @@ -356,14 +345,14 @@ server_require_peer_cert_allow_partial_chain() -> server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ClientCAs), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), PartialChain = fun(CertChain) -> case lists:member(IntermidiateCA, CertChain) of @@ -398,12 +387,12 @@ server_require_peer_cert_do_not_allow_partial_chain() -> server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), PartialChain = fun(_CertChain) -> unknown_ca @@ -439,12 +428,12 @@ server_require_peer_cert_partial_chain_fun_fail() -> server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), PartialChain = fun(_CertChain) -> ture = false %% crash on purpose @@ -479,8 +468,8 @@ verify_fun_always_run_client() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -524,8 +513,8 @@ verify_fun_always_run_client(Config) when is_list(Config) -> verify_fun_always_run_server() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% If user verify fun is called correctly we fail the connection. @@ -573,63 +562,28 @@ cert_expired() -> [{doc,"Test server with expired certificate"}]. cert_expired(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join(PrivDir, "server/expired_cert.pem"), - [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), - OTPCert = public_key:pkix_decode_cert(DerCert, otp), - OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, - {Year, Month, Day} = date(), - {Hours, Min, Sec} = time(), - NotBeforeStr = lists:flatten(io_lib:format("~p~s~s~s~s~sZ",[Year-2, - two_digits_str(Month), - two_digits_str(Day), - two_digits_str(Hours), - two_digits_str(Min), - two_digits_str(Sec)])), - NotAfterStr = lists:flatten(io_lib:format("~p~s~s~s~s~sZ",[Year-1, - two_digits_str(Month), - two_digits_str(Day), - two_digits_str(Hours), - two_digits_str(Min), - two_digits_str(Sec)])), - NewValidity = {'Validity', {generalTime, NotBeforeStr}, {generalTime, NotAfterStr}}, - - ct:log("Validity: ~p ~n NewValidity: ~p ~n", - [OTPTbsCert#'OTPTBSCertificate'.validity, NewValidity]), - - NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{validity = NewValidity}, - NewServerDerCert = public_key:pkix_sign(NewOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - + Active = proplists:get_value(active, Config), + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_ca_0, + [{validity, {{Year-2, Month, Day}, + {Year-1, Month, Day}}}]}], + Config, "_expired"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, - {options, NewServerOpts}]), + {options, [{active, Active}| ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {options, [{verify, verify_peer} | ClientOpts]}]), - receive - {Client, {error, {tls_alert, "certificate expired"}}} -> - receive - {Server, {error, {tls_alert, "certificate expired"}}} -> - ok; - {Server, {error, closed}} -> - ok - end - end. + {options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]), + + tcp_delivery_workaround(Server, {error, {tls_alert, "certificate expired"}}, + Client, {error, {tls_alert, "certificate expired"}}). two_digits_str(N) when N < 10 -> lists:flatten(io_lib:format("0~p", [N])); @@ -638,60 +592,32 @@ two_digits_str(N) -> %%-------------------------------------------------------------------- extended_key_usage_verify_server() -> - [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode for server"}]. - -extended_key_usage_verify_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), + [{doc,"Test cert that has a critical extended_key_usage extension in server cert"}]. + +extended_key_usage_verify_server(Config) when is_list(Config) -> + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_peer_opts, + [{extensions, + [{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], true}] + }]}], Config, "_keyusage_server"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join(PrivDir, "server/new_cert.pem"), - [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), - ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), - ServerExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-serverAuth']}, - ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, - ServerExtensions = ServerOTPTbsCert#'OTPTBSCertificate'.extensions, - NewServerOTPTbsCert = ServerOTPTbsCert#'OTPTBSCertificate'{extensions = - [ServerExtKeyUsageExt | - ServerExtensions]}, - NewServerDerCert = public_key:pkix_sign(NewServerOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - - ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join(PrivDir, "client/new_cert.pem"), - [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), - ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), - ClientExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-clientAuth']}, - ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, - ClientExtensions = ClientOTPTbsCert#'OTPTBSCertificate'.extensions, - NewClientOTPTbsCert = ClientOTPTbsCert#'OTPTBSCertificate'{extensions = - [ClientExtKeyUsageExt | - ClientExtensions]}, - NewClientDerCert = public_key:pkix_sign(NewClientOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]), - NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | NewServerOpts]}]), + {options, [{verify, verify_none}, {active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | - NewClientOpts]}]), + {options, [{verify, verify_peer}, {active, Active} | + ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -699,60 +625,35 @@ extended_key_usage_verify_server(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -extended_key_usage_verify_client() -> +extended_key_usage_verify_both() -> [{doc,"Test cert that has a critical extended_key_usage extension in client verify_peer mode"}]. -extended_key_usage_verify_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), +extended_key_usage_verify_both(Config) when is_list(Config) -> + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_peer_opts, + [{extensions, [{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], true}] + }]}, + {client_peer_opts, + [{extensions, [{?'id-ce-extKeyUsage', + [?'id-kp-clientAuth'], true}] + }]}], Config, "_keyusage_both"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join(PrivDir, "server/new_cert.pem"), - [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), - ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), - ServerExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-serverAuth']}, - ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, - ServerExtensions = ServerOTPTbsCert#'OTPTBSCertificate'.extensions, - NewServerOTPTbsCert = ServerOTPTbsCert#'OTPTBSCertificate'{extensions = - [ServerExtKeyUsageExt | - ServerExtensions]}, - NewServerDerCert = public_key:pkix_sign(NewServerOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - - ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join(PrivDir, "client/new_cert.pem"), - [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), - ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), - ClientExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-clientAuth']}, - ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, - ClientExtensions = ClientOTPTbsCert#'OTPTBSCertificate'.extensions, - NewClientOTPTbsCert = ClientOTPTbsCert#'OTPTBSCertificate'{extensions = - [ClientExtKeyUsageExt | - ClientExtensions]}, - NewClientDerCert = public_key:pkix_sign(NewClientOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]), - NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + {options, [{verify, verify_peer}, {active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -764,132 +665,103 @@ critical_extension_verify_server() -> [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. critical_extension_verify_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{client_peer_opts, + [{extensions, [{{2,16,840,1,113730,1,1}, + <<3,2,6,192>>, true}] + }]}], Config, "_client_unknown_extension"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), - add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - - ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), - add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), - NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error( [{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | NewServerOpts]}]), + {options, [{verify, verify_peer}, {active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error( [{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_none}, {active, Active} | ClientOpts]}]), %% This certificate has a critical extension that we don't - %% understand. Therefore, verification should fail. - tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, - Client, {error, {tls_alert, "unsupported certificate"}}), + %% understand. Therefore, verification should fail. - ssl_test_lib:close(Server), - ok. + tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, + Client, {error, {tls_alert, "unsupported certificate"}}), + + ssl_test_lib:close(Server). %%-------------------------------------------------------------------- critical_extension_verify_client() -> [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. critical_extension_verify_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_peer_opts, + [{extensions, [{{2,16,840,1,113730,1,1}, + <<3,2,6,192>>, true}] + }]}], Config, "_server_unknown_extensions"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), - add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - - ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), - add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), - NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error( [{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + {options, [{verify, verify_none}, {active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error( [{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]), %% This certificate has a critical extension that we don't %% understand. Therefore, verification should fail. - tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, - Client, {error, {tls_alert, "unsupported certificate"}}), + ssl_test_lib:check_result(Server, {error, {tls_alert, "unsupported certificate"}}, + Client, {error, {tls_alert, "unsupported certificate"}}), + + ssl_test_lib:close(Server). - ssl_test_lib:close(Server), - ok. %%-------------------------------------------------------------------- critical_extension_verify_none() -> [{doc,"Test cert that has a critical unknown extension in verify_none mode"}]. critical_extension_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{client_peer_opts, + [{extensions, + [{{2,16,840,1,113730,1,1}, + <<3,2,6,192>>, true}] + }]}], Config, "_unknown_extensions"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), - add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), - NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], - - ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), - add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), - NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server( [{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + {options, [{verify, verify_none}, {active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client( [{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_none}, {active, Active} | ClientOpts]}]), %% This certificate has a critical extension that we don't %% understand. But we're using `verify_none', so verification @@ -897,28 +769,7 @@ critical_extension_verify_none(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ok. - -add_critical_netscape_cert_type(CertFile, NewCertFile, KeyFile) -> - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(CertFile), - OTPCert = public_key:pkix_decode_cert(DerCert, otp), - %% This is the "Netscape Cert Type" extension, telling us that the - %% certificate can be used for SSL clients and SSL servers. - NetscapeCertTypeExt = #'Extension'{ - extnID = {2,16,840,1,113730,1,1}, - critical = true, - extnValue = <<3,2,6,192>>}, - OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, - Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions, - NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{ - extensions = [NetscapeCertTypeExt] ++ Extensions}, - NewDerCert = public_key:pkix_sign(NewOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewCertFile, [{'Certificate', NewDerCert, not_encrypted}]), - ok. + ssl_test_lib:close(Client). %%-------------------------------------------------------------------- no_authority_key_identifier() -> @@ -926,35 +777,21 @@ no_authority_key_identifier() -> " but are present in trusted certs db."}]. no_authority_key_identifier(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - CertFile = proplists:get_value(certfile, ServerOpts), - NewCertFile = filename:join(PrivDir, "server/new_cert.pem"), - [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(CertFile), - OTPCert = public_key:pkix_decode_cert(DerCert, otp), - OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, - Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions, - NewExtensions = delete_authority_key_extension(Extensions, []), - NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{extensions = NewExtensions}, - - ct:log("Extensions ~p~n, NewExtensions: ~p~n", [Extensions, NewExtensions]), - - NewDerCert = public_key:pkix_sign(NewOTPTbsCert, Key), - ssl_test_lib:der_to_pem(NewCertFile, [{'Certificate', NewDerCert, not_encrypted}]), - NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)], + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_peer_opts, + [{extensions, [{auth_key_id, undefined}] + }]}, + {client_peer_opts, + [{extensions, [{auth_key_id, undefined}] + }]}], Config, "_peer_no_auth_key_id"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, NewServerOpts}]), + {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -970,53 +807,35 @@ no_authority_key_identifier(Config) when is_list(Config) -> delete_authority_key_extension([], Acc) -> lists:reverse(Acc); delete_authority_key_extension([#'Extension'{extnID = ?'id-ce-authorityKeyIdentifier'} | Rest], - Acc) -> + Acc) -> delete_authority_key_extension(Rest, Acc); delete_authority_key_extension([Head | Rest], Acc) -> delete_authority_key_extension(Rest, [Head | Acc]). %%-------------------------------------------------------------------- -no_authority_key_identifier_and_nonstandard_encoding() -> - [{doc, "Test cert with nonstandard encoding that does not have" - " authorityKeyIdentifier extension but are present in trusted certs db."}]. - -no_authority_key_identifier_and_nonstandard_encoding(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), - [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), - Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), - - CertFile = proplists:get_value(certfile, ServerOpts), - NewCertFile = filename:join(PrivDir, "server/new_cert.pem"), - [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(CertFile), - ServerCert = public_key:pkix_decode_cert(DerCert, plain), - ServerTbsCert = ServerCert#'Certificate'.tbsCertificate, - Extensions0 = ServerTbsCert#'TBSCertificate'.extensions, - %% need to remove authorityKeyIdentifier extension to cause DB lookup by signature - Extensions = delete_authority_key_extension(Extensions0, []), - NewExtensions = replace_key_usage_extension(Extensions, []), - NewServerTbsCert = ServerTbsCert#'TBSCertificate'{extensions = NewExtensions}, - - ct:log("Extensions ~p~n, NewExtensions: ~p~n", [Extensions, NewExtensions]), - - TbsDer = public_key:pkix_encode('TBSCertificate', NewServerTbsCert, plain), - Sig = public_key:sign(TbsDer, md5, Key), - NewServerCert = ServerCert#'Certificate'{tbsCertificate = NewServerTbsCert, signature = Sig}, - NewDerCert = public_key:pkix_encode('Certificate', NewServerCert, plain), - ssl_test_lib:der_to_pem(NewCertFile, [{'Certificate', NewDerCert, not_encrypted}]), - NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)], - +no_authority_key_identifier_keyEncipherment() -> + [{doc, "Test cert with keyEncipherment key_usage an no" + " authorityKeyIdentifier extension, but are present in trusted certs db."}]. + +no_authority_key_identifier_keyEncipherment(Config) when is_list(Config) -> + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_peer_opts, + [{extensions, [{auth_key_id, undefined}, + {key_usage, [digitalSignature, + keyEncipherment]}] + }]}, + {client_peer_opts, + [{extensions, [{auth_key_id, undefined}] + }]}], Config, "_peer_keyEncipherment"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{active, true} | NewServerOpts]}]), + {options, [{active, true} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -1028,14 +847,6 @@ no_authority_key_identifier_and_nonstandard_encoding(Config) when is_list(Config ssl_test_lib:close(Server), ssl_test_lib:close(Client). -replace_key_usage_extension([], Acc) -> - lists:reverse(Acc); -replace_key_usage_extension([#'Extension'{extnID = ?'id-ce-keyUsage'} = E | Rest], Acc) -> - %% A nonstandard DER encoding of [digitalSignature, keyEncipherment] - Val = <<3, 2, 0, 16#A0>>, - replace_key_usage_extension(Rest, [E#'Extension'{extnValue = Val} | Acc]); -replace_key_usage_extension([Head | Rest], Acc) -> - replace_key_usage_extension(Rest, [Head | Acc]). %%-------------------------------------------------------------------- @@ -1043,16 +854,16 @@ invalid_signature_server() -> [{doc,"Test client with invalid signature"}]. invalid_signature_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), - KeyFile = filename:join(PrivDir, "server/key.pem"), + KeyFile = proplists:get_value(keyfile, ServerOpts), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ServerCertFile = proplists:get_value(certfile, ServerOpts), - NewServerCertFile = filename:join(PrivDir, "server/invalid_cert.pem"), + NewServerCertFile = filename:join(PrivDir, "server_invalid_cert.pem"), [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, @@ -1071,8 +882,8 @@ invalid_signature_server(Config) when is_list(Config) -> {from, self()}, {options, [{verify, verify_peer} | ClientOpts]}]), - tcp_delivery_workaround(Server, {error, {tls_alert, "bad certificate"}}, - Client, {error, {tls_alert, "bad certificate"}}). + tcp_delivery_workaround(Server, {error, {tls_alert, "unknown ca"}}, + Client, {error, {tls_alert, "unknown ca"}}). %%-------------------------------------------------------------------- @@ -1080,16 +891,16 @@ invalid_signature_client() -> [{doc,"Test server with invalid signature"}]. invalid_signature_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), - KeyFile = filename:join(PrivDir, "client/key.pem"), + KeyFile = proplists:get_value(keyfile, ClientOpts), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), ClientCertFile = proplists:get_value(certfile, ClientOpts), - NewClientCertFile = filename:join(PrivDir, "client/invalid_cert.pem"), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, @@ -1108,8 +919,8 @@ invalid_signature_client(Config) when is_list(Config) -> {from, self()}, {options, NewClientOpts}]), - tcp_delivery_workaround(Server, {error, {tls_alert, "bad certificate"}}, - Client, {error, {tls_alert, "bad certificate"}}). + tcp_delivery_workaround(Server, {error, {tls_alert, "unknown ca"}}, + Client, {error, {tls_alert, "unknown ca"}}). %%-------------------------------------------------------------------- @@ -1118,8 +929,14 @@ client_with_cert_cipher_suites_handshake() -> [{doc, "Test that client with a certificate without keyEncipherment usage " " extension can connect to a server with restricted cipher suites "}]. client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts_digital_signature_only, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{client_peer_opts, + [{extensions, + [{key_usage, [digitalSignature]}] + }]}], Config, "_sign_only_extensions"), + + + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1148,7 +965,7 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> server_verify_no_cacerts() -> [{doc,"Test server must have cacerts if it wants to verify client"}]. server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_opts, Config)), + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_rsa_opts, Config)), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1163,7 +980,7 @@ unknown_server_ca_fail() -> [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. unknown_server_ca_fail(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1207,7 +1024,7 @@ unknown_server_ca_accept_verify_none() -> [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1232,7 +1049,7 @@ unknown_server_ca_accept_verify_peer() -> " with a verify_fun that accepts the unknown ca error"}]. unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1271,7 +1088,7 @@ unknown_server_ca_accept_backwardscompatibility() -> [{doc,"Test that old style verify_funs will work"}]. unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index e293d183f7..668c76e38d 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -155,9 +155,15 @@ init_per_testcase(Case, Config0) -> DataDir = proplists:get_value(data_dir, Config), CertDir = filename:join(proplists:get_value(priv_dir, Config0), idp_crl), {CertOpts, Config} = init_certs(CertDir, idp_crl, Config), - {ok, _} = make_certs:all(DataDir, CertDir, CertOpts), - ct:timetrap({seconds, 6}), - [{cert_dir, CertDir} | Config]; + case make_certs:all(DataDir, CertDir, CertOpts) of + {ok, _} -> + ct:timetrap({seconds, 6}), + [{cert_dir, CertDir} | Config]; + _ -> + end_per_testcase(Case, Config0), + ssl_test_lib:clean_start(), + {skip, "Unable to create IDP crls"} + end; false -> end_per_testcase(Case, Config0), ssl_test_lib:clean_start(), diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 3446a566c4..7281425461 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -1973,14 +1973,14 @@ passive_recv_packet(Socket, _, 0) -> {error, timeout} = ssl:recv(Socket, 0, 500), ok; Other -> - {other, Other, ssl:session_info(Socket), 0} + {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), 0} end; passive_recv_packet(Socket, Data, N) -> case ssl:recv(Socket, 0) of {ok, Data} -> passive_recv_packet(Socket, Data, N-1); Other -> - {other, Other, ssl:session_info(Socket), N} + {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), N} end. send(Socket,_, 0) -> @@ -2032,7 +2032,7 @@ active_once_packet(Socket,_, 0) -> {ssl, Socket, []} -> ok; {ssl, Socket, Other} -> - {other, Other, ssl:session_info(Socket), 0} + {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), 0} end; active_once_packet(Socket, Data, N) -> receive @@ -2077,7 +2077,7 @@ active_packet(Socket, _, 0) -> {ssl, Socket, []} -> ok; Other -> - {other, Other, ssl:session_info(Socket), 0} + {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), 0} end; active_packet(Socket, Data, N) -> receive @@ -2089,7 +2089,7 @@ active_packet(Socket, Data, N) -> {ssl, Socket, Data} -> active_packet(Socket, Data, N -1); Other -> - {other, Other, ssl:session_info(Socket),N} + {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]),N} end. assert_packet_opt(Socket, Type) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 56b8a8a22d..3b9073ac0b 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -34,13 +34,13 @@ run_where(_) -> ClientNode = node(), ServerNode = node(), - {ok, Host} = rpc:call(ServerNode, inet, gethostname, []), + Host = rpc:call(ServerNode, net_adm, localhost, []), {ClientNode, ServerNode, Host}. run_where(_, ipv6) -> ClientNode = node(), ServerNode = node(), - {ok, Host} = rpc:call(ServerNode, inet, gethostname, []), + Host = rpc:call(ServerNode, net_adm, localhost, []), {ClientNode, ServerNode, Host}. node_to_hostip(Node) -> @@ -485,29 +485,115 @@ make_dsa_cert(Config) -> {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} | Config]. +make_rsa_cert_chains(ChainConf, Config, Suffix) -> + CryptoSupport = crypto:supports(), + KeyGenSpec = key_gen_info(rsa, rsa), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa" ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa" ++ Suffix]), + GenCertData = x509_test:gen_test_certs([{digest, appropriate_sha(CryptoSupport)} | KeyGenSpec] ++ ChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + {[{verify, verify_peer} | ClientConf], + [{reuseaddr, true}, {verify, verify_peer} | ServerConf] + }. + +make_ec_cert_chains(ChainConf, ClientChainType, ServerChainType, Config) -> + CryptoSupport = crypto:supports(), + KeyGenSpec = key_gen_info(ClientChainType, ServerChainType), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ClientChainType)]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ServerChainType)]), + GenCertData = x509_test:gen_test_certs([{digest, appropriate_sha(CryptoSupport)} | KeyGenSpec] ++ ChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + {[{verify, verify_peer} | ClientConf], + [{reuseaddr, true}, {verify, verify_peer} | ServerConf] + }. + +key_gen_info(ClientChainType, ServerChainType) -> + key_gen_spec("client", ClientChainType) ++ key_gen_spec("server", ServerChainType). + +key_gen_spec(Role, ecdh_rsa) -> + CurveOid = hd(tls_v1:ecc_curves(0)), + [{list_to_atom(Role ++ "_key_gen"), {namedCurve, CurveOid}}, + {list_to_atom(Role ++ "_key_gen_chain"), [hardcode_rsa_key(1), + {namedCurve, CurveOid}]} + ]; +key_gen_spec(Role, ecdhe_ecdsa) -> + CurveOid = hd(tls_v1:ecc_curves(0)), + [{list_to_atom(Role ++ "_key_gen"), {namedCurve, CurveOid}}, + {list_to_atom(Role ++ "_key_gen_chain"), [{namedCurve, CurveOid}, + {namedCurve, CurveOid}]} + ]; +key_gen_spec(Role, ecdh_ecdsa) -> + CurveOid = hd(tls_v1:ecc_curves(0)), + [{list_to_atom(Role ++ "_key_gen"), {namedCurve, CurveOid}}, + {list_to_atom(Role ++ "_key_gen_chain"), [{namedCurve, CurveOid}, + {namedCurve, CurveOid}]} + ]; +key_gen_spec(Role, ecdhe_rsa) -> + [{list_to_atom(Role ++ "_key_gen"), hardcode_rsa_key(1)}, + {list_to_atom(Role ++ "_key_gen_chain"), [hardcode_rsa_key(2), + hardcode_rsa_key(3)]} + ]; +key_gen_spec(Role, rsa) -> + [{list_to_atom(Role ++ "_key_gen"), hardcode_rsa_key(1)}, + {list_to_atom(Role ++ "_key_gen_chain"), [hardcode_rsa_key(2), + hardcode_rsa_key(3)]} + ]. make_ecdsa_cert(Config) -> CryptoSupport = crypto:supports(), case proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)) of - true -> - {ServerCaCertFile, ServerCertFile, ServerKeyFile} = - make_cert_files("server", Config, ec, ec, "", [{digest, appropriate_sha(CryptoSupport)}]), - {ClientCaCertFile, ClientCertFile, ClientKeyFile} = - make_cert_files("client", Config, ec, ec, "", [{digest, appropriate_sha(CryptoSupport)}]), - [{server_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, - {server_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ClientCaCertFile}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {verify, verify_peer}]}, - {client_ecdsa_opts, [{ssl_imp, new}, - {cacertfile, ClientCaCertFile}, - {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} + true -> + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa"]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa"]), + CurveOid = hd(tls_v1:ecc_curves(0)), + GenCertData = x509_test:gen_test_certs([{server_key_gen, {namedCurve, CurveOid}}, + {client_key_gen, {namedCurve, CurveOid}}, + {server_key_gen_chain, [{namedCurve, CurveOid}, + {namedCurve, CurveOid}]}, + {client_key_gen_chain, [{namedCurve, CurveOid}, + {namedCurve, CurveOid}]}, + {digest, appropriate_sha(CryptoSupport)}]), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + [{server_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]}, + + {server_ecdsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, + {verify, verify_peer} | ServerConf]}, + {client_ecdsa_opts, ClientConf} | Config]; - _ -> + false -> + Config + end. +make_rsa_cert(Config) -> + CryptoSupport = crypto:supports(), + case proplists:get_bool(rsa, proplists:get_value(public_keys, CryptoSupport)) of + true -> + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa"]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa"]), + GenCertData = x509_test:gen_test_certs([{server_key_gen, hardcode_rsa_key(1)}, + {client_key_gen, hardcode_rsa_key(2)}, + {server_key_gen_chain, [hardcode_rsa_key(3), + hardcode_rsa_key(4)]}, + {client_key_gen_chain, [hardcode_rsa_key(5), + hardcode_rsa_key(6)]}, + {digest, appropriate_sha(CryptoSupport)}]), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + [{server_rsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]}, + + {server_rsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, + {verify, verify_peer} | ServerConf]}, + {client_rsa_opts, ClientConf}, + {client_rsa_verify_opts, [{verify, verify_peer} |ClientConf]} + | Config]; + false -> Config end. - appropriate_sha(CryptoSupport) -> case proplists:get_bool(sha256, CryptoSupport) of true -> @@ -524,21 +610,30 @@ make_ecdh_rsa_cert(Config) -> CryptoSupport = crypto:supports(), case proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)) of true -> - {ServerCaCertFile, ServerCertFile, ServerKeyFile} = - make_cert_files("server", Config, rsa, ec, "rsa_", []), - {ClientCaCertFile, ClientCertFile, ClientKeyFile} = - make_cert_files("client", Config, rsa, ec, "rsa_",[]), - [{server_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, - {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ClientCaCertFile}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {verify, verify_peer}]}, - {client_ecdh_rsa_opts, [{ssl_imp, new}, - {cacertfile, ClientCaCertFile}, - {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} - | Config]; + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdh_rsa"]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdh_rsa"]), + CurveOid = hd(tls_v1:ecc_curves(0)), + GenCertData = x509_test:gen_test_certs([{server_key_gen, {namedCurve, CurveOid}}, + {client_key_gen, {namedCurve, CurveOid}}, + {server_key_gen_chain, [hardcode_rsa_key(1), + {namedCurve, CurveOid} + ]}, + {client_key_gen_chain, [hardcode_rsa_key(2), + {namedCurve, CurveOid} + ]}, + {digest, appropriate_sha(CryptoSupport)}]), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + + [{server_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]}, + + {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, + {verify, verify_peer} | ServerConf]}, + + {client_ecdh_rsa_opts, ClientConf} + + | Config]; _ -> Config end. @@ -782,18 +877,18 @@ no_result(_) -> no_result_msg. trigger_renegotiate(Socket, [ErlData, N]) -> - [{session_id, Id} | _ ] = ssl:session_info(Socket), + {ok, [{session_id, Id}]} = ssl:connection_information(Socket, [session_id]), trigger_renegotiate(Socket, ErlData, N, Id). trigger_renegotiate(Socket, _, 0, Id) -> ct:sleep(1000), - case ssl:session_info(Socket) of - [{session_id, Id} | _ ] -> + case ssl:connection_information(Socket, [session_id]) of + {ok, [{session_id, Id}]} -> fail_session_not_renegotiated; %% Tests that uses this function will not reuse %% sessions so if we get a new session id the %% renegotiation has succeeded. - [{session_id, _} | _ ] -> + {ok, [{session_id, _}]} -> ok; {error, closed} -> fail_session_fatal_alert_during_renegotiation; @@ -858,9 +953,9 @@ available_suites(Version) -> rsa_non_signed_suites(Version) -> lists:filter(fun({rsa, _, _}) -> - true; + false; (_) -> - false + true end, available_suites(Version)). @@ -914,6 +1009,12 @@ openssl_ecdh_rsa_suites() -> lists:filter(fun(Str) -> string_regex_filter(Str, "ECDH-RSA") end, Ciphers). +openssl_filter(FilterStr) -> + Ciphers = string:tokens(os:cmd("openssl ciphers"), ":"), + lists:filter(fun(Str) -> string_regex_filter(Str, FilterStr) + end, Ciphers). + + string_regex_filter(Str, Search) when is_list(Str) -> case re:run(Str, Search, []) of nomatch -> @@ -998,8 +1099,8 @@ cipher_result(Socket, Result) -> end. session_info_result(Socket) -> - ssl:session_info(Socket). - + {ok, Info} = ssl:connection_information(Socket, [session_id, cipher_suite]), + Info. public_key(#'PrivateKeyInfo'{privateKeyAlgorithm = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?rsaEncryption}, @@ -1079,6 +1180,21 @@ sufficient_crypto_support(Group) when Group == ciphers_ec; %% From ssl_basic sufficient_crypto_support(_) -> true. +check_key_exchange_send_active(Socket, false) -> + send_recv_result_active(Socket); +check_key_exchange_send_active(Socket, KeyEx) -> + {ok, [{cipher_suite, Suite}]} = ssl:connection_information(Socket, [cipher_suite]), + true = check_key_exchange(Suite, KeyEx), + send_recv_result_active(Socket). + +check_key_exchange({KeyEx,_, _}, KeyEx) -> + true; +check_key_exchange({KeyEx,_,_,_}, KeyEx) -> + true; +check_key_exchange(KeyEx1, KeyEx2) -> + ct:pal("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]), + false. + send_recv_result_active(Socket) -> ssl:send(Socket, "Hello world"), receive @@ -1178,6 +1294,8 @@ check_sane_openssl_version(Version) -> case supports_ssl_tls_version(Version) of true -> case {Version, os:cmd("openssl version")} of + {'sslv3', "OpenSSL 1.0.2" ++ _} -> + false; {_, "OpenSSL 1.0.2" ++ _} -> true; {_, "OpenSSL 1.0.1" ++ _} -> @@ -1190,6 +1308,10 @@ check_sane_openssl_version(Version) -> false; {'tlsv1.1', "OpenSSL 0" ++ _} -> false; + {'dtlsv1', "OpenSSL 0" ++ _} -> + false; + {'dtlsv1.2', "OpenSSL 0" ++ _} -> + false; {_, _} -> true end; @@ -1295,6 +1417,21 @@ portable_open_port(Exe, Args) -> open_port({spawn_executable, AbsPath}, [{args, Args}, stderr_to_stdout]). +supports_ssl_tls_version(sslv2 = Version) -> + case os:cmd("openssl version") of + "OpenSSL 1" ++ _ -> + false; + %% Appears to be broken + "OpenSSL 0.9.8.o" ++ _ -> + false; + _ -> + VersionFlag = version_flag(Version), + Exe = "openssl", + Args = ["s_client", VersionFlag], + Port = ssl_test_lib:portable_open_port(Exe, Args), + do_supports_ssl_tls_version(Port) + end; + supports_ssl_tls_version(Version) -> VersionFlag = version_flag(Version), Exe = "openssl", @@ -1304,6 +1441,8 @@ supports_ssl_tls_version(Version) -> do_supports_ssl_tls_version(Port) -> receive + {Port, {data, "u"}} -> + false; {Port, {data, "unknown option" ++ _}} -> false; {Port, {data, Data}} -> @@ -1313,14 +1452,17 @@ do_supports_ssl_tls_version(Port) -> false -> do_supports_ssl_tls_version(Port) end - after 500 -> + after 1000 -> true end. -ssl_options(Option, Config) -> +ssl_options(Option, Config) when is_atom(Option) -> ProtocolOpts = proplists:get_value(protocol_opts, Config, []), Opts = proplists:get_value(Option, Config, []), - Opts ++ ProtocolOpts. + Opts ++ ProtocolOpts; +ssl_options(Options, Config) -> + ProtocolOpts = proplists:get_value(protocol_opts, Config, []), + Options ++ ProtocolOpts. protocol_version(Config) -> protocol_version(Config, atom). @@ -1397,6 +1539,76 @@ tls_version('dtlsv1.2' = Atom) -> tls_version(Atom) -> tls_record:protocol_version(Atom). +hardcode_rsa_key(1) -> + {'RSAPrivateKey',0, + 23995666614853919027835084074500048897452890537492185072956789802729257783422306095699263934587064480357348855732149402060270996295002843755712064937715826848741191927820899197493902093529581182351132392364214171173881547273475904587683433713767834856230531387991145055273426806331200574039205571401702219159773947658558490957010003143162250693492642996408861265758000254664396313741422909188635443907373976005987612936763564996605457102336549804831742940035613780926178523017685712710473543251580072875247250504243621640157403744718833162626193206685233710319205099867303242759099560438381385658382486042995679707669, + 17, + 11292078406990079542510627799764728892919007311761028269626724613049062486316379339152594792746853873109340637991599718616598115903530750002688030558925094987642913848386305504703012749896273497577003478759630198199473669305165131570674557041773098755873191241407597673069847908861741446606684974777271632545629600685952292605647052193819136445675100211504432575554351515262198132231537860917084269870590492135731720141577986787033006338680118008484613510063003323516659048210893001173583018220214626635609151105287049126443102976056146630518124476470236027123782297108342869049542023328584384300970694412006494684657, + 169371138592582642967021557955633494538845517070305333860805485424261447791289944610138334410987654265476540480228705481960508520379619587635662291973699651583489223555422528867090299996446070521801757353675026048850480903160224210802452555900007597342687137394192939372218903554801584969667104937092080815197, + 141675062317286527042995673340952251894209529891636708844197799307963834958115010129693036021381525952081167155681637592199810112261679449166276939178032066869788822014115556349519329537177920752776047051833616197615329017439297361972726138285974555338480581117881706656603857310337984049152655480389797687577, + 119556097830058336212015217380447172615655659108450823901745048534772786676204666783627059584226579481512852103690850928442711896738555003036938088452023283470698275450886490965004917644550167427154181661417665446247398284583687678213495921811770068712485038160606780733330990744565824684470897602653233516609, + 41669135975672507953822256864985956439473391144599032012999352737636422046504414744027363535700448809435637398729893409470532385959317485048904982111185902020526124121798693043976273393287623750816484427009887116945685005129205106462566511260580751570141347387612266663707016855981760014456663376585234613993, + 76837684977089699359024365285678488693966186052769523357232308621548155587515525857011429902602352279058920284048929101483304120686557782043616693940283344235057989514310975192908256494992960578961614059245280827077951132083993754797053182279229469590276271658395444955906108899267024101096069475145863928441, + asn1_NOVALUE}; + +hardcode_rsa_key(2) -> +{'RSAPrivateKey',0, + 21343679768589700771839799834197557895311746244621307033143551583788179817796325695589283169969489517156931770973490560582341832744966317712674900833543896521418422508485833901274928542544381247956820115082240721897193055368570146764204557110415281995205343662628196075590438954399631753508888358737971039058298703003743872818150364935790613286541190842600031570570099801682794056444451081563070538409720109449780410837763602317050353477918147758267825417201591905091231778937606362076129350476690460157227101296599527319242747999737801698427160817755293383890373574621116766934110792127739174475029121017282777887777, + 17, + 18832658619343853622211588088997845201745658451136447382185486691577805721584993260814073385267196632785528033211903435807948675951440868570007265441362261636545666919252206383477878125774454042314841278013741813438699754736973658909592256273895837054592950290554290654932740253882028017801960316533503857992358685308186680144968293076156011747178275038098868263178095174694099811498968993700538293188879611375604635940554394589807673542938082281934965292051746326331046224291377703201248790910007232374006151098976879987912446997911775904329728563222485791845480864283470332826504617837402078265424772379987120023773, + 146807662748886761089048448970170315054939768171908279335181627815919052012991509112344782731265837727551849787333310044397991034789843793140419387740928103541736452627413492093463231242466386868459637115999163097726153692593711599245170083315894262154838974616739452594203727376460632750934355508361223110419, + 145385325050081892763917667176962991350872697916072592966410309213561884732628046256782356731057378829876640317801978404203665761131810712267778698468684631707642938779964806354584156202882543264893826268426566901882487709510744074274965029453915224310656287149777603803201831202222853023280023478269485417083, + 51814469205489445090252393754177758254684624060673510353593515699736136004585238510239335081623236845018299924941168250963996835808180162284853901555621683602965806809675350150634081614988136541809283687999704622726877773856604093851236499993845033701707873394143336209718962603456693912094478414715725803677, + 51312467664734785681382706062457526359131540440966797517556579722433606376221663384746714140373192528191755406283051201483646739222992016094510128871300458249756331334105225772206172777487956446433115153562317730076172132768497908567634716277852432109643395464627389577600646306666889302334125933506877206029, + 30504662229874176232343608562807118278893368758027179776313787938167236952567905398252901545019583024374163153775359371298239336609182249464886717948407152570850677549297935773605431024166978281486607154204888016179709037883348099374995148481968169438302456074511782717758301581202874062062542434218011141540, + asn1_NOVALUE}; + +hardcode_rsa_key(3) -> +{'RSAPrivateKey',0, + 25089040456112869869472694987833070928503703615633809313972554887193090845137746668197820419383804666271752525807484521370419854590682661809972833718476098189250708650325307850184923546875260207894844301992963978994451844985784504212035958130279304082438876764367292331581532569155681984449177635856426023931875082020262146075451989132180409962870105455517050416234175675478291534563995772675388370042873175344937421148321291640477650173765084699931690748536036544188863178325887393475703801759010864779559318631816411493486934507417755306337476945299570726975433250753415110141783026008347194577506976486290259135429, + 17, + 8854955455098659953931539407470495621824836570223697404931489960185796768872145882893348383311931058684147950284994536954265831032005645344696294253579799360912014817761873358888796545955974191021709753644575521998041827642041589721895044045980930852625485916835514940558187965584358347452650930302268008446431977397918214293502821599497633970075862760001650736520566952260001423171553461362588848929781360590057040212831994258783694027013289053834376791974167294527043946669963760259975273650548116897900664646809242902841107022557239712438496384819445301703021164043324282687280801738470244471443835900160721870265, + 171641816401041100605063917111691927706183918906535463031548413586331728772311589438043965564336865070070922328258143588739626712299625805650832695450270566547004154065267940032684307994238248203186986569945677705100224518137694769557564475390859269797990555863306972197736879644001860925483629009305104925823, + 146170909759497809922264016492088453282310383272504533061020897155289106805616042710009332510822455269704884883705830985184223718261139908416790475825625309815234508695722132706422885088219618698987115562577878897003573425367881351537506046253616435685549396767356003663417208105346307649599145759863108910523, + 60579464612132153154728441333538327425711971378777222246428851853999433684345266860486105493295364142377972586444050678378691780811632637288529186629507258781295583787741625893888579292084087601124818789392592131211843947578009918667375697196773859928702549128225990187436545756706539150170692591519448797349, + 137572620950115585809189662580789132500998007785886619351549079675566218169991569609420548245479957900898715184664311515467504676010484619686391036071176762179044243478326713135456833024206699951987873470661533079532774988581535389682358631768109586527575902839864474036157372334443583670210960715165278974609, + 15068630434698373319269196003209754243798959461311186548759287649485250508074064775263867418602372588394608558985183294561315208336731894947137343239541687540387209051236354318837334154993136528453613256169847839789803932725339395739618592522865156272771578671216082079933457043120923342632744996962853951612, + asn1_NOVALUE}; +hardcode_rsa_key(4) -> +{'RSAPrivateKey',0, + 28617237755030755643854803617273584643843067580642149032833640135949799721163782522787597288521902619948688786051081993247908700824196122780349730169173433743054172191054872553484065655968335396052034378669869864779940355219732200954630251223541048434478476115391643898092650304645086338265930608997389611376417609043761464100338332976874588396803891301015812818307951159858145399281035705713082131199940309445719678087542976246147777388465712394062188801177717719764254900022006288880246925156931391594131839991579403409541227225173269459173129377291869028712271737734702830877034334838181789916127814298794576266389, + 17, + 26933870828264240605980991639786903194205240075898493207372837775011576208154148256741268036255908348187001210401018346586267012540419880263858569570986761169933338532757527109161473558558433313931326474042230460969355628442100895016122589386862163232450330461545076609969553227901257730132640573174013751883368376011370428995523268034111482031427024082719896108094847702954695363285832195666458915142143884210891427766607838346722974883433132513540317964796373298134261669479023445911856492129270184781873446960437310543998533283339488055776892320162032014809906169940882070478200435536171854883284366514852906334641, + 177342190816702392178883147766999616783253285436834252111702533617098994535049411784501174309695427674025956656849179054202187436663487378682303508229883753383891163725167367039879190685255046547908384208614573353917213168937832054054779266431207529839577747601879940934691505396807977946728204814969824442867, + 161367340863680900415977542864139121629424927689088951345472941851682581254789586032968359551717004797621579428672968948552429138154521719743297455351687337112710712475376510559020211584326773715482918387500187602625572442687231345855402020688502483137168684570635690059254866684191216155909970061793538842967, + 62591361464718491357252875682470452982324688977706206627659717747211409835899792394529826226951327414362102349476180842659595565881230839534930649963488383547255704844176717778780890830090016428673547367746320007264898765507470136725216211681602657590439205035957626212244060728285168687080542875871702744541, + 28476589564178982426348978152495139111074987239250991413906989738532220221433456358759122273832412611344984605059935696803369847909621479954699550944415412431654831613301737157474154985469430655673456186029444871051571607533040825739188591886206320553618003159523945304574388238386685203984112363845918619347, + 34340318160575773065401929915821192439103777558577109939078671096408836197675640654693301707202885840826672396546056002756167635035389371579540325327619480512374920136684787633921441576901246290213545161954865184290700344352088099063404416346968182170720521708773285279884132629954461545103181082503707725012, + asn1_NOVALUE}; +hardcode_rsa_key(5) -> +{'RSAPrivateKey',0, + 26363170152814518327068346871197765236382539835597898797762992537312221863402655353436079974302838986536256364057947538018476963115004626096654613827403121905035011992899481598437933532388248462251770039307078647864188314916665766359828262009578648593031111569685489178543405615478739906285223620987558499488359880003693226535420421293716164794046859453204135383236667988765227190694994861629971618548127529849059769249520775574008363789050621665120207265361610436965088511042779948238320901918522125988916609088415989475825860046571847719492980547438560049874493788767083330042728150253120940100665370844282489982633, + 17, + 10855423004100095781734025182257903332628104638187370093196526338893267826106975733767797636477639582691399679317978398007608161282648963686857782164224814902073240232370374775827384395689278778574258251479385325591136364965685903795223402003944149420659869469870495544106108194608892902588033255700759382142132115013969680562678811046675523365751498355532768935784747314021422035957153013494814430893022253205880275287307995039363642554998244274484818208792520243113824379110193356010059999642946040953102866271737127640405568982049887176990990501963784502429481034227543991366980671390566584211881030995602076468001, + 163564135568104310461344551909369650951960301778977149705601170951529791054750122905880591964737953456660497440730575925978769763154927541340839715938951226089095007207042122512586007411328664679011914120351043948122025612160733403945093961374276707993674792189646478659304624413958625254578122842556295400709, + 161179405627326572739107057023381254841260287988433675196680483761672455172873134522398837271764104320975746111042211695289319249471386600030523328069395763313848583139553961129874895374324504709512019736703349829576024049432816885712623938437949550266365056310544300920756181033500610331519029869549723159637, + 115457036871603042678596154288966812436677860079277988027483179495197499568058910286503947269226790675289762899339230065396778656344654735064122152427494983121714122734382674714766593466820233891067233496718383963380253373289929461608301619793607087995535147427985749641862087821617853120878674947686796753441, + 142217122612346975946270932667689342506994371754500301644129838613240401623123353990351915239791856753802128921507833848784693455415929352968108818884760967629866396887841730408713142977345151214275311532385308673155315337734838428569962298621720191411498579097539089047726042088382891468987379296661520434973, + 40624877259097915043489529504071755460170951428490878553842519165800720914888257733191322215286203357356050737713125202129282154441426952501134581314792133018830748896123382106683994268028624341502298766844710276939303555637478596035491641473828661569958212421472263269629366559343208764012473880251174832392, + asn1_NOVALUE}; +hardcode_rsa_key(6) -> +{'RSAPrivateKey',0, + 22748888494866396715768692484866595111939200209856056370972713870125588774286266397044592487895293134537316190976192161177144143633669641697309689280475257429554879273045671863645233402796222694405634510241820106743648116753479926387434021380537483429927516962909367257212902212159798399531316965145618774905828756510318897899298783143203190245236381440043169622358239226123652592179006905016804587837199618842875361941208299410035232803124113612082221121192550063791073372276763648926636149384299189072950588522522800393261949880796214514243704858378436010975184294077063518776479282353562934591448646412389762167039, + 17, + 6690849557313646092873144848490175032923294179369428344403739373566349639495960705013115437616262686628622409110644753287395336362844012263914614494257428655751435080307550548130951000822418439531068973600535325512837681398082331290421770994275730420566916753796872722709677121223470117509210872101652580854566448661533030419787125312956120661097410038933324613372774190658239039998357548275441758790939430824924502690997433186652165055694361752689819209062683281242276039100201318203707142383491769671330743466041394101421674581185260900666085723130684175548215193875544802254923825103844262661010117443222587769713, + 164748737139489923768181260808494855987398781964531448608652166632780898215212977127034263859971474195908846263894581556691971503119888726148555271179103885786024920582830105413607436718060544856016793981261118694063993837665813285582095833772675610567592660039821387740255651489996976698808018635344299728063, + 138082323967104548254375818343885141517788525705334488282154811252858957969378263753268344088034079842223206527922445018725900110643394926788280539200323021781309918753249061620424428562366627334409266756720941754364262467100514166396917565961434203543659974860389803369482625510495464845206228470088664021953, + 19382204369351755737433089506881747763223386113474288071606137250915399790025056132592266336467232258342217207517009594904937823896457497193947678962247515974826461245038835931012639613889475865413740468383661022831058098548919210068481862796785365949128548239978986792971253116470232552800943368864035262125, + 48734937870742781736838524121371226418043009072470995864289933383361985165662916618800592031070851709019955245149098241903258862580021738866451955011878713569874088971734962924855680669070574353320917678842685325069739694270769705787147376221682660074232932303666989424523279591939575827719845342384234360689, + 81173034184183681160439870161505779100040258708276674532866007896310418779840630960490793104541748007902477778658270784073595697910785917474138815202903114440800310078464142273778315781957021015333260021813037604142367434117205299831740956310682461174553260184078272196958146289378701001596552915990080834227, + asn1_NOVALUE}. + + dtls_hello() -> [1, <<0,1,4>>, diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 42cc8b57ad..04c86ccbb6 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -88,9 +88,8 @@ dtls_all_versions_tests() -> %%erlang_client_openssl_server, erlang_server_openssl_client, %%erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert - %% This one works but gets port EXIT first some times - %%erlang_server_openssl_client_reuse_session + erlang_server_openssl_client_dsa_cert, + erlang_server_openssl_client_reuse_session %%erlang_client_openssl_server_renegotiate, %%erlang_client_openssl_server_nowrap_seqnum, %%erlang_server_openssl_client_nowrap_seqnum, @@ -168,13 +167,18 @@ init_per_group(basic, Config) -> init_per_group(GroupName, Config) -> case ssl_test_lib:is_tls_version(GroupName) of true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - _ -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> ssl:start(), Config end. @@ -334,7 +338,7 @@ basic_erlang_server_openssl_client(Config) when is_list(Config) -> ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), V2Compat = proplists:get_value(v2_hello_compatible, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", ct:pal("v2_hello_compatible: ~p", [V2Compat]), @@ -347,7 +351,8 @@ basic_erlang_server_openssl_client(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Exe = "openssl", - Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port) | workaround_openssl_s_clinent()], + Args = ["s_client", "-connect", hostname_format(Hostname) ++ + ":" ++ integer_to_list(Port) | workaround_openssl_s_clinent()], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), @@ -406,7 +411,7 @@ erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", @@ -418,7 +423,7 @@ erlang_server_openssl_client(Config) when is_list(Config) -> Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client", "-connect", "localhost: " ++ integer_to_list(Port), + Args = ["s_client", "-connect", hostname_format(Hostname) ++":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -482,7 +487,7 @@ erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", CaCertFile = proplists:get_value(cacertfile, ClientOpts), @@ -496,7 +501,7 @@ erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client", "-connect", "localhost: " ++ integer_to_list(Port), + Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version), "-cert", CertFile, "-CAfile", CaCertFile, @@ -521,7 +526,7 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", @@ -534,7 +539,8 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + Args = ["s_client", "-connect", hostname_format(Hostname) + ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version), "-reconnect"], @@ -651,7 +657,7 @@ erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", @@ -665,7 +671,7 @@ erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client","-connect", "localhost: " ++ integer_to_list(Port), + Args = ["s_client","-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version), "-msg"], @@ -775,7 +781,7 @@ erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", @@ -795,7 +801,7 @@ erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> Exe = "openssl", Args = ["s_client", "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile,"-connect", "localhost:" ++ integer_to_list(Port), + "-key", KeyFile,"-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -978,37 +984,21 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Data = "From openssl to erlang", - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Exe = "openssl", - Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), "-ssl2", "-msg"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), - receive - {'EXIT', OpenSslPort, _} = Exit -> - ct:log("Received: ~p ~n", [Exit]), - ok - end, - receive - {'EXIT', _, _} = UnkownExit -> - Msg = lists:flatten(io_lib:format("Received: ~p ~n", [UnkownExit])), - ct:log(Msg), - ct:comment(Msg), - ok - after 0 -> - ok - end, + consume_port_exit(OpenSslPort), ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}), process_flag(trap_exit, false). %%-------------------------------------------------------------------- @@ -1022,7 +1012,7 @@ ssl2_erlang_server_openssl_client_comp(Config) when is_list(Config) -> ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", @@ -1032,27 +1022,14 @@ ssl2_erlang_server_openssl_client_comp(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Exe = "openssl", - Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), "-ssl2", "-msg"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), - receive - {'EXIT', OpenSslPort, _} = Exit -> - ct:log("Received: ~p ~n", [Exit]), - ok - end, - receive - {'EXIT', _, _} = UnkownExit -> - Msg = lists:flatten(io_lib:format("Received: ~p ~n", [UnkownExit])), - ct:log(Msg), - ct:comment(Msg), - ok - after 0 -> - ok - end, + consume_port_exit(OpenSslPort), ssl_test_lib:check_result(Server, {error, {tls_alert, "protocol version"}}), process_flag(trap_exit, false). @@ -1686,7 +1663,7 @@ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callbac ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1697,7 +1674,8 @@ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callbac Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", "localhost:" + Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", + hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -1715,7 +1693,7 @@ start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenS ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = ErlangServerOpts ++ ServerOpts0, - {_, ServerNode, _} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1726,8 +1704,9 @@ start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenS Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", "localhost:" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], + Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", + hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -1873,3 +1852,17 @@ openssl_client_args(false, Hostname, Port, ServerName) -> openssl_client_args(true, Hostname, Port, ServerName) -> ["s_client", "-no_ssl2", "-connect", Hostname ++ ":" ++ integer_to_list(Port), "-servername", ServerName]. + +consume_port_exit(OpenSSLPort) -> + receive + {'EXIT', OpenSSLPort, _} -> + ok + end. + +hostname_format(Hostname) -> + case lists:member($., Hostname) of + true -> + Hostname; + false -> + "localhost" + end. diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl new file mode 100644 index 0000000000..4da1537ef6 --- /dev/null +++ b/lib/ssl/test/x509_test.erl @@ -0,0 +1,352 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + + -module(x509_test). + + -include_lib("public_key/include/public_key.hrl"). + + -export([gen_test_certs/1, gen_pem_config_files/3]). + + gen_test_certs(Opts) -> + SRootKey = gen_key(proplists:get_value(server_key_gen, Opts)), + CRootKey = gen_key(proplists:get_value(client_key_gen, Opts)), + ServerRoot = root_cert("server", SRootKey, Opts), + ClientRoot = root_cert("client", CRootKey, Opts), + [{ServerCert, ServerKey} | ServerCAsKeys] = config(server, ServerRoot, SRootKey, Opts), + [{ClientCert, ClientKey} | ClientCAsKeys] = config(client, ClientRoot, CRootKey, Opts), + ServerCAs = ca_config(ClientRoot, ServerCAsKeys), + ClientCAs = ca_config(ServerRoot, ClientCAsKeys), + [{server_config, [{cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCAs}]}, + {client_config, [{cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCAs}]}]. + +gen_pem_config_files(GenCertData, ClientBase, ServerBase) -> + ServerConf = proplists:get_value(server_config, GenCertData), + ClientConf = proplists:get_value(client_config, GenCertData), + + ServerCaCertFile = ServerBase ++ "_server_cacerts.pem", + ServerCertFile = ServerBase ++ "_server_cert.pem", + ServerKeyFile = ServerBase ++ "_server_key.pem", + + ClientCaCertFile = ClientBase ++ "_client_cacerts.pem", + ClientCertFile = ClientBase ++ "_client_cert.pem", + ClientKeyFile = ClientBase ++ "_client_key.pem", + + do_gen_pem_config_files(ServerConf, + ServerCertFile, + ServerKeyFile, + ServerCaCertFile), + do_gen_pem_config_files(ClientConf, + ClientCertFile, + ClientKeyFile, + ClientCaCertFile), + [{server_config, [{certfile, ServerCertFile}, + {keyfile, ServerKeyFile}, {cacertfile, ServerCaCertFile}]}, + {client_config, [{certfile, ClientCertFile}, + {keyfile, ClientKeyFile}, {cacertfile, ClientCaCertFile}]}]. + + + do_gen_pem_config_files(Config, CertFile, KeyFile, CAFile) -> + CAs = proplists:get_value(cacerts, Config), + Cert = proplists:get_value(cert, Config), + Key = proplists:get_value(key, Config), + der_to_pem(CertFile, [cert_entry(Cert)]), + der_to_pem(KeyFile, [key_entry(Key)]), + der_to_pem(CAFile, ca_entries(CAs)). + + 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}. + + ca_entries(CAs) -> + [{'Certificate', CACert, not_encrypted} || CACert <- CAs]. + + gen_key(KeyGen) -> + case is_key(KeyGen) of + true -> + KeyGen; + false -> + public_key:generate_key(KeyGen) + end. + +root_cert(Role, PrivKey, Opts) -> + TBS = cert_template(), + Issuer = issuer("root", Role, " ROOT CA"), + OTPTBS = TBS#'OTPTBSCertificate'{ + signature = sign_algorithm(PrivKey, Opts), + issuer = Issuer, + validity = validity(Opts), + subject = Issuer, + subjectPublicKeyInfo = public_key(PrivKey), + extensions = extensions(Role, ca, Opts) + }, + public_key:pkix_sign(OTPTBS, PrivKey). + +config(Role, Root, Key, Opts) -> + KeyGenOpt = list_to_atom(atom_to_list(Role) ++ "_key_gen_chain"), + KeyGens = proplists:get_value(KeyGenOpt, Opts, default_key_gen()), + Keys = lists:map(fun gen_key/1, KeyGens), + cert_chain(Role, Root, Key, Opts, Keys). + +cert_template() -> + #'OTPTBSCertificate'{ + version = v3, + serialNumber = trunc(rand:uniform()*100000000)*10000 + 1, + issuerUniqueID = asn1_NOVALUE, + subjectUniqueID = asn1_NOVALUE + }. + +issuer(Contact, Role, Name) -> + subject(Contact, Role ++ Name). + +subject(Contact, Name) -> + Opts = [{email, Contact ++ "@erlang.org"}, + {name, Name}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "automated testing"}], + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +subject_enc({name, Name}) -> + {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> + {?'id-emailAddress', Email}; +subject_enc({city, City}) -> + {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> + {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> + {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> + {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> + {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> + {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> + {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> + {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> + Other. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> + lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) + end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +extensions(Role, Type, Opts) -> + Exts = proplists:get_value(extensions, Opts, []), + lists:flatten([extension(Ext) || Ext <- default_extensions(Role, Type, Exts)]). + +%% Common extension: name_constraints, policy_constraints, ext_key_usage, inhibit_any, +%% auth_key_id, subject_key_id, policy_mapping, + +default_extensions(_, ca, Exts) -> + Def = [{key_usage, [keyCertSign, cRLSign]}, + {basic_constraints, default}], + add_default_extensions(Def, Exts); + +default_extensions(server, peer, Exts) -> + Hostname = net_adm:localhost(), + Def = [{key_usage, [digitalSignature, keyAgreement]}, + {subject_alt, Hostname}], + add_default_extensions(Def, Exts); + +default_extensions(_, peer, Exts) -> + Exts. + +add_default_extensions(Def, Exts) -> + Filter = fun({Key, _}, D) -> + lists:keydelete(Key, 1, D); + ({Key, _, _}, D) -> + lists:keydelete(Key, 1, D) + end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> + []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint = Len}, + critical = true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({auth_key_id, {Oid, Issuer, SNr}}) -> + #'Extension'{extnID = ?'id-ce-authorityKeyIdentifier', + extnValue = #'AuthorityKeyIdentifier'{ + keyIdentifier = Oid, + authorityCertIssuer = Issuer, + authorityCertSerialNumber = SNr}, + critical = false}; +extension({key_usage, Value}) -> + #'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = Value, + critical = false}; +extension({subject_alt, Hostname}) -> + #'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = [{dNSName, Hostname}], + critical = false}; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + +public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}; +public_key(#'ECPrivateKey'{version = _Version, + privateKey = _PrivKey, + parameters = Params, + publicKey = PubKey}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = #'ECPoint'{point = PubKey}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)), + #'SignatureAlgorithm'{algorithm = Type, + parameters = 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + #'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1', + parameters = {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; +sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) -> + Type = ecdsa_digest_oid(proplists:get_value(digest, Opts, sha1)), + #'SignatureAlgorithm'{algorithm = Type, + parameters = Parms}. + +rsa_digest_oid(sha1) -> + ?'sha1WithRSAEncryption'; +rsa_digest_oid(sha512) -> + ?'sha512WithRSAEncryption'; +rsa_digest_oid(sha384) -> + ?'sha384WithRSAEncryption'; +rsa_digest_oid(sha256) -> + ?'sha256WithRSAEncryption'; +rsa_digest_oid(md5) -> + ?'md5WithRSAEncryption'. + +ecdsa_digest_oid(sha1) -> + ?'ecdsa-with-SHA1'; +ecdsa_digest_oid(sha512) -> + ?'ecdsa-with-SHA512'; +ecdsa_digest_oid(sha384) -> + ?'ecdsa-with-SHA384'; +ecdsa_digest_oid(sha256) -> + ?'ecdsa-with-SHA256'. + +ca_config(Root, CAsKeys) -> + [Root | [CA || {CA, _} <- CAsKeys]]. + +cert_chain(Role, Root, RootKey, Opts, Keys) -> + cert_chain(Role, Root, RootKey, Opts, Keys, 0, []). + +cert_chain(Role, IssuerCert, IssuerKey, Opts, [Key], _, Acc) -> + PeerOpts = list_to_atom(atom_to_list(Role) ++ "_peer_opts"), + Cert = cert(Role, public_key:pkix_decode_cert(IssuerCert, otp), + IssuerKey, Key, "admin", " Peer cert", Opts, PeerOpts, peer), + [{Cert, Key}, {IssuerCert, IssuerKey} | Acc]; +cert_chain(Role, IssuerCert, IssuerKey, Opts, [Key | Keys], N, Acc) -> + CAOpts = list_to_atom(atom_to_list(Role) ++ "_ca_" ++ integer_to_list(N)), + Cert = cert(Role, public_key:pkix_decode_cert(IssuerCert, otp), IssuerKey, Key, "webadmin", + " Intermidiate CA " ++ integer_to_list(N), Opts, CAOpts, ca), + cert_chain(Role, Cert, Key, Opts, Keys, N+1, [{IssuerCert, IssuerKey} | Acc]). + +cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Issuer, + serialNumber = SNr + }}, + PrivKey, Key, Contact, Name, Opts, CertOptsName, Type) -> + CertOpts = proplists:get_value(CertOptsName, Opts, []), + TBS = cert_template(), + OTPTBS = TBS#'OTPTBSCertificate'{ + signature = sign_algorithm(PrivKey, Opts), + issuer = Issuer, + validity = validity(CertOpts), + subject = subject(Contact, atom_to_list(Role) ++ Name), + subjectPublicKeyInfo = public_key(Key), + extensions = extensions(Role, Type, + add_default_extensions([{auth_key_id, {auth_key_oid(Role), Issuer, SNr}}], + CertOpts)) + }, + public_key:pkix_sign(OTPTBS, PrivKey). + +is_key(#'DSAPrivateKey'{}) -> + true; +is_key(#'RSAPrivateKey'{}) -> + true; +is_key(#'ECPrivateKey'{}) -> + true; +is_key(_) -> + false. + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). + +default_key_gen() -> + case tls_v1:ecc_curves(0) of + [] -> + [{rsa, 2048, 17}, {rsa, 2048, 17}]; + [_|_] -> + [{namedCurve, hd(tls_v1:ecc_curves(0))}, + {namedCurve, hd(tls_v1:ecc_curves(0))}] + end. + +auth_key_oid(server) -> + ?'id-kp-serverAuth'; +auth_key_oid(client) -> + ?'id-kp-clientAuth'. diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 7c2d9bec48..25b2a2bec0 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 8.1.3 +SSL_VSN = 8.2 |