diff options
author | Dan Gudmundsson <[email protected]> | 2010-04-20 12:00:00 +0200 |
---|---|---|
committer | Björn Gustavsson <[email protected]> | 2010-08-20 08:50:41 +0200 |
commit | f0dbde23b539999add8754ec84541698419fc8b5 (patch) | |
tree | c2924c60a51335f1b25ef6493b8013fb6de52c7a /lib/ssl | |
parent | b792ebc90dca5c9ba3d75d7f3c56e2295ae5d6f3 (diff) | |
download | otp-f0dbde23b539999add8754ec84541698419fc8b5.tar.gz otp-f0dbde23b539999add8754ec84541698419fc8b5.tar.bz2 otp-f0dbde23b539999add8754ec84541698419fc8b5.zip |
public_key, ssl: Patch 1112
OTP-7046 Support for Diffie-Hellman. ssl-3.11 requires public_key-0.6.
OTP-8553 Moved extended key usage test for ssl values to ssl.
OTP-8557 Fixes handling of the option fail_if_no_peer_cert and some
undocumented options. Thanks to Rory Byrne.
OTP-7046 Support for Diffie-Hellman. ssl-3.11 requires public_key-0.6.
OTP-8517 New ssl now properly handles ssl renegotiation, and initiates
a renegotiation if ssl/ltls-sequence numbers comes close
to the max value. However RFC-5746 is not yet supported,
but will be in an upcoming release.
OTP-8545 When gen_tcp is configured with the {packet,http} option,
it automatically switches to expect HTTP Headers after a
HTTP Request/Response line has been received. This update
fixes ssl to behave in the same way. Thanks to Rory Byrne.
OTP-8554 Ssl now correctly verifies the extended_key_usage extension
and also allows the user to verify application specific
extensions by supplying an appropriate fun.
OTP-8560 Fixed ssl:transport_accept/2 to return properly when socket
is closed. Thanks to Rory Byrne.
Diffstat (limited to 'lib/ssl')
-rw-r--r-- | lib/ssl/doc/src/new_ssl.xml | 25 | ||||
-rw-r--r-- | lib/ssl/doc/src/notes.xml | 61 | ||||
-rw-r--r-- | lib/ssl/src/ssl.appup.src | 6 | ||||
-rw-r--r-- | lib/ssl/src/ssl.erl | 76 | ||||
-rw-r--r-- | lib/ssl/src/ssl_certificate.erl | 53 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 853 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 193 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.hrl | 13 | ||||
-rw-r--r-- | lib/ssl/src/ssl_internal.hrl | 14 | ||||
-rw-r--r-- | lib/ssl/src/ssl_record.erl | 64 | ||||
-rw-r--r-- | lib/ssl/src/ssl_record.hrl | 17 | ||||
-rw-r--r-- | lib/ssl/src/ssl_ssl3.erl | 18 | ||||
-rw-r--r-- | lib/ssl/src/ssl_tls1.erl | 20 | ||||
-rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 936 | ||||
-rw-r--r-- | lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem | 5 | ||||
-rw-r--r-- | lib/ssl/test/ssl_packet_SUITE.erl | 174 | ||||
-rw-r--r-- | lib/ssl/test/ssl_test_lib.erl | 172 | ||||
-rw-r--r-- | lib/ssl/test/ssl_to_openssl_SUITE.erl | 362 | ||||
-rw-r--r-- | lib/ssl/vsn.mk | 12 |
19 files changed, 2291 insertions, 783 deletions
diff --git a/lib/ssl/doc/src/new_ssl.xml b/lib/ssl/doc/src/new_ssl.xml index b642280096..08868a1b3c 100644 --- a/lib/ssl/doc/src/new_ssl.xml +++ b/lib/ssl/doc/src/new_ssl.xml @@ -84,8 +84,6 @@ <item>New API functions are ssl:shutdown/2, ssl:cipher_suites/[0,1] and ssl:versions/0</item> - <item>Diffie-Hellman keyexchange is - not supported yet.</item> <item>CRL and policy certificate extensions are not supported yet. </item> <item>Supported SSL/TLS-versions are SSL-3.0 and TLS-1.0 </item> @@ -118,8 +116,8 @@ {fail_if_no_peer_cert, boolean()} {depth, integer()} | {certfile, path()} | {keyfile, path()} | {password, string()} | - {cacertfile, path()} | {ciphers, ciphers()} | {ssl_imp, ssl_imp()} - | {reuse_sessions, boolean()} | {reuse_session, fun()} + {cacertfile, path()} | {dhfile, path()} | {ciphers, ciphers()} | + {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} </c></p> <p><c>transportoption() = {CallbackModule, DataTag, ClosedTag} @@ -262,6 +260,12 @@ end CA certificates (trusted certificates used for verifying a peer certificate). May be omitted if you do not want to verify the peer.</item> + + <tag>{dhfile, path()}</tag> + <item>Path to file containing PEM encoded Diffie Hellman parameters, + for the server to use if a cipher suite using Diffie Hellman key exchange + is negotiated. If not specified hardcode parameters will be used. + </item> <tag>{ciphers, ciphers()}</tag> <item>The function <c>ciphers_suites/0</c> can @@ -491,6 +495,19 @@ end </func> <func> + <name>renegotiate(Socket) -> ok | {error, Reason}</name> + <fsummary> Initiates a new handshake.</fsummary> + <type> + <v>Socket = sslsocket()</v> + </type> + <desc><p>Initiates a new handshake. A notable return value is + <c>{error, renegotiation_rejected}</c> indicating that the peer + refused to go through with the renegotiation but the connection + is still active using the previously negotiated session.</p> + </desc> + </func> + + <func> <name>send(Socket, Data) -> ok | {error, Reason}</name> <fsummary>Write data to a socket.</fsummary> <type> diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 2dd11bc88e..9d13427677 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -30,6 +30,67 @@ </header> <p>This document describes the changes made to the SSL application. </p> +<section><title>SSL 3.11</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixes handling of the option fail_if_no_peer_cert and + some undocumented options. Thanks to Rory Byrne.</p> + <p> + Own Id: OTP-8557</p> + </item> + </list> + </section> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Support for Diffie-Hellman. ssl-3.11 requires + public_key-0.6.</p> + <p> + Own Id: OTP-7046</p> + </item> + <item> + <p> + New ssl now properly handles ssl renegotiation, and + initiates a renegotiation if ssl/ltls-sequence numbers + comes close to the max value. However RFC-5746 is not yet + supported, but will be in an upcoming release.</p> + <p> + Own Id: OTP-8517</p> + </item> + <item> + <p> + When gen_tcp is configured with the {packet,http} option, + it automatically switches to expect HTTP Headers after a + HTTP Request/Response line has been received. This update + fixes ssl to behave in the same way. Thanks to Rory + Byrne.</p> + <p> + Own Id: OTP-8545</p> + </item> + <item> + <p> + Ssl now correctly verifies the extended_key_usage + extension and also allows the user to verify application + specific extensions by supplying an appropriate fun.</p> + <p> + Own Id: OTP-8554 Aux Id: OTP-8553 </p> + </item> + <item> + <p> + Fixed ssl:transport_accept/2 to return properly when + socket is closed. Thanks to Rory Byrne.</p> + <p> + Own Id: OTP-8560</p> + </item> + </list> + </section> + +</section> <section><title>SSL 3.10.9</title> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index fdda65021d..e8ae6846aa 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -9,7 +9,8 @@ {"3.10.5", [{restart_application, ssl}]}, {"3.10.6", [{restart_application, ssl}]}, {"3.10.7", [{restart_application, ssl}]}, - {"3.10.8", [{restart_application, ssl}]} + {"3.10.8", [{restart_application, ssl}]}, + {"3.10.9", [{restart_application, ssl}]} ], [ {"3.10", [{restart_application, ssl}]}, @@ -19,6 +20,7 @@ {"3.10.4", [{restart_application, ssl}]}, {"3.10.5", [{restart_application, ssl}]}, {"3.10.6", [{restart_application, ssl}]}, - {"3.10.8", [{restart_application, ssl}]} + {"3.10.8", [{restart_application, ssl}]}, + {"3.10.9", [{restart_application, ssl}]} ]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index de74c91505..3cd4c7fdbd 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -29,13 +29,15 @@ connect/3, connect/2, connect/4, connection_info/1, controlling_process/2, listen/2, pid/1, peername/1, recv/2, recv/3, send/2, getopts/2, setopts/2, seed/1, sockname/1, peercert/1, - peercert/2, version/0, versions/0, session_info/1, format_error/1]). + peercert/2, version/0, versions/0, session_info/1, format_error/1, + renegotiate/1]). %% Should be deprecated as soon as old ssl is removed %%-deprecated({pid, 1, next_major_release}). -include("ssl_int.hrl"). -include("ssl_internal.hrl"). +-include("ssl_record.hrl"). -record(config, {ssl, %% SSL parameters inet_user, %% User set inet options @@ -151,18 +153,23 @@ transport_accept(#sslsocket{pid = {ListenSocket, #config{cb=CbInfo, ssl=SslOpts} %% and options should be inherited. EmOptions = emulated_options(), {ok, InetValues} = inet:getopts(ListenSocket, EmOptions), - {CbModule,_,_} = CbInfo, - {ok, Socket} = CbModule:accept(ListenSocket, Timeout), - inet:setopts(Socket, internal_inet_values()), - {ok, Port} = inet:port(Socket), - case ssl_connection_sup:start_child([server, "localhost", Port, Socket, - {SslOpts, socket_options(InetValues)}, self(), - CbInfo]) of - {ok, Pid} -> - CbModule:controlling_process(Socket, Pid), - {ok, SslSocket#sslsocket{pid = Pid}}; - {error, Reason} -> - {error, Reason} + ok = inet:setopts(ListenSocket, internal_inet_values()), + {CbModule,_,_} = CbInfo, + case CbModule:accept(ListenSocket, Timeout) of + {ok, Socket} -> + ok = inet:setopts(ListenSocket, InetValues), + {ok, Port} = inet:port(Socket), + ConnArgs = [server, "localhost", Port, Socket, + {SslOpts, socket_options(InetValues)}, self(), CbInfo], + case ssl_connection_sup:start_child(ConnArgs) of + {ok, Pid} -> + CbModule:controlling_process(Socket, Pid), + {ok, SslSocket#sslsocket{pid = Pid}}; + {error, Reason} -> + {error, Reason} + end; + {error, Reason} -> + {error, Reason} end; transport_accept(#sslsocket{} = ListenSocket, Timeout) -> @@ -436,6 +443,10 @@ versions() -> AvailableVsns = ?DEFAULT_SUPPORTED_VERSIONS, [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. + +renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) -> + ssl_connection:renegotiation(Pid). + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -509,6 +520,9 @@ handle_options(Opts0, Role) -> end end, + UserFailIfNoPeerCert = validate_option(fail_if_no_peer_cert, + proplists:get_value(fail_if_no_peer_cert, Opts, false)), + {Verify, FailIfNoPeerCert, CaCertDefault} = %% Handle 0, 1, 2 for backwards compatibility case proplists:get_value(verify, Opts, verify_none) of @@ -521,9 +535,7 @@ handle_options(Opts0, Role) -> verify_none -> {verify_none, false, ca_cert_default(verify_none, Role)}; verify_peer -> - {verify_peer, proplists:get_value(fail_if_no_peer_cert, - Opts, false), - ca_cert_default(verify_peer, Role)}; + {verify_peer, UserFailIfNoPeerCert, ca_cert_default(verify_peer, Role)}; Value -> throw({error, {eoptions, {verify, Value}}}) end, @@ -534,28 +546,31 @@ handle_options(Opts0, Role) -> versions = handle_option(versions, Opts, []), verify = validate_option(verify, Verify), verify_fun = handle_option(verify_fun, Opts, VerifyFun), - fail_if_no_peer_cert = validate_option(fail_if_no_peer_cert, - FailIfNoPeerCert), + fail_if_no_peer_cert = FailIfNoPeerCert, verify_client_once = handle_option(verify_client_once, Opts, false), + validate_extensions_fun = handle_option(validate_extensions_fun, Opts, undefined), depth = handle_option(depth, Opts, 1), certfile = CertFile, keyfile = handle_option(keyfile, Opts, CertFile), key = handle_option(key, Opts, undefined), password = handle_option(password, Opts, ""), cacertfile = handle_option(cacertfile, Opts, CaCertDefault), + dhfile = handle_option(dhfile, Opts, undefined), ciphers = handle_option(ciphers, Opts, []), %% Server side option reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), reuse_sessions = handle_option(reuse_sessions, Opts, true), + renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), debug = handle_option(debug, Opts, []) }, CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed}), - SslOptions = [versions, verify, verify_fun, + SslOptions = [versions, verify, verify_fun, validate_extensions_fun, + fail_if_no_peer_cert, verify_client_once, depth, certfile, keyfile, - key, password, cacertfile, ciphers, + key, password, cacertfile, dhfile, ciphers, debug, reuse_session, reuse_sessions, ssl_imp, - cd_info], + cb_info, renegotiate_at], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -585,6 +600,9 @@ validate_option(fail_if_no_peer_cert, Value) validate_option(verify_client_once, Value) when Value == true; Value == false -> Value; + +validate_option(validate_extensions_fun, Value) when Value == undefined; is_function(Value) -> + Value; validate_option(depth, Value) when is_integer(Value), Value >= 0, Value =< 255-> Value; @@ -605,11 +623,17 @@ validate_option(cacertfile, undefined) -> ""; validate_option(cacertfile, Value) when is_list(Value), Value =/= "" -> Value; +validate_option(dhfile, undefined = Value) -> + Value; +validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> + Value; validate_option(ciphers, Value) when is_list(Value) -> Version = ssl_record:highest_protocol_version([]), try cipher_suites(Version, Value) catch exit:_ -> + throw({error, {eoptions, {ciphers, Value}}}); + error:_-> throw({error, {eoptions, {ciphers, Value}}}) end; validate_option(reuse_session, Value) when is_function(Value) -> @@ -617,6 +641,9 @@ validate_option(reuse_session, Value) when is_function(Value) -> validate_option(reuse_sessions, Value) when Value == true; Value == false -> Value; +validate_option(renegotiate_at, Value) when is_integer(Value) -> + min(Value, ?DEFAULT_RENEGOTIATE_AT); + validate_option(debug, Value) when is_list(Value); Value == true -> Value; validate_option(Opt, Value) -> @@ -628,7 +655,7 @@ validate_versions([Version | Rest], Versions) when Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> validate_versions(Rest, Versions); -validate_versions(Ver, Versions) -> +validate_versions([Ver| _], Versions) -> throw({error, {eoptions, {Ver, {versions, Versions}}}}). validate_inet_option(mode, Value) @@ -832,6 +859,11 @@ version() -> Vsns end, {ok, {SSLVsn, CompVsn, LibVsn}}. + +min(N,M) when N < M -> + N; +min(_, M) -> + M. %% Only used to remove exit messages from old ssl %% First is a nonsense clause to provide some diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index d97b61a5ce..686e90a70c 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -29,10 +29,12 @@ -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("ssl_debug.hrl"). +-include_lib("public_key/include/public_key.hrl"). -export([trusted_cert_and_path/3, certificate_chain/2, - file_to_certificats/1]). + file_to_certificats/1, + validate_extensions/6]). %%==================================================================== %% Internal application API @@ -87,6 +89,30 @@ file_to_certificats(File) -> {ok, List} = ssl_manager:cache_pem_file(File), [Bin || {cert, Bin, not_encrypted} <- List]. + +%% Validates ssl/tls specific extensions +validate_extensions([], ValidationState, UnknownExtensions, _, AccErr, _) -> + {UnknownExtensions, ValidationState, AccErr}; + +validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = KeyUse, + critical = true} | Rest], + ValidationState, UnknownExtensions, Verify, AccErr0, Role) -> + case is_valid_extkey_usage(KeyUse, Role) of + true -> + validate_extensions(Rest, ValidationState, UnknownExtensions, + Verify, AccErr0, Role); + false -> + AccErr = + not_valid_extension({bad_cert, invalid_ext_key_usage}, Verify, AccErr0), + validate_extensions(Rest, ValidationState, UnknownExtensions, Verify, AccErr, Role) + end; + +validate_extensions([Extension | Rest], ValidationState, UnknownExtensions, + Verify, AccErr, Role) -> + validate_extensions(Rest, ValidationState, [Extension | UnknownExtensions], + Verify, AccErr, Role). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -154,3 +180,18 @@ not_valid(Alert, true, _) -> throw(Alert); not_valid(_, false, {ErlCert, Path}) -> {ErlCert, Path, [{bad_cert, unknown_ca}]}. + +is_valid_extkey_usage(KeyUse, client) -> + %% Client wants to verify server + is_valid_key_usage(KeyUse,?'id-kp-serverAuth'); +is_valid_extkey_usage(KeyUse, server) -> + %% Server wants to verify client + is_valid_key_usage(KeyUse, ?'id-kp-clientAuth'). + +is_valid_key_usage(KeyUse, Use) -> + lists:member(Use, KeyUse). + +not_valid_extension(Error, true, _) -> + throw(Error); +not_valid_extension(Error, false, AccErrors) -> + [Error | AccErrors]. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 4847fd278d..8ff001b172 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -41,15 +41,14 @@ %% Internal application API -export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2, new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, - peer_certificate/1, - sockname/1, peername/1]). + peer_certificate/1, sockname/1, peername/1, renegotiation/1]). %% Called by ssl_connection_sup -export([start_link/7]). %% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, connection/2, connection/3, abbreviated/2, - handle_event/3, +-export([init/1, hello/2, certify/2, cipher/2, connection/2, + abbreviated/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -record(state, { @@ -78,17 +77,25 @@ client_certificate_requested = false, key_algorithm, % atom as defined by cipher_suite public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams} - private_key, % PKIX: 'RSAPrivateKey' - diffie_hellman_params, % + private_key, % PKIX: #'RSAPrivateKey'{} + diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side + diffie_hellman_keys, % {PublicKey, PrivateKey} premaster_secret, % cert_db_ref, % ets_table() from, % term(), where to reply bytes_to_read, % integer(), # bytes to read in passive mode user_data_buffer, % binary() %% tls_buffer, % Keeps a lookahead one packet if available - log_alert % boolan() + log_alert, % boolean() + renegotiation, % {boolean(), From | internal | peer} + recv_during_renegotiation, %boolean() + send_queue % queue() }). +-define(DEFAULT_DIFFIE_HELLMAN_PARAMS, + #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, + base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). + %%==================================================================== %% Internal application API %%==================================================================== @@ -99,15 +106,15 @@ %% Description: %%-------------------------------------------------------------------- send(Pid, Data) -> - sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity). + sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity). send(Pid, Data, Timeout) -> - sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout). + sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout). %%-------------------------------------------------------------------- %% Function: %% %% Description: %%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> % TODO: Prio with renegotiate? +recv(Pid, Length, Timeout) -> sync_send_all_state_event(Pid, {recv, Length}, Timeout). %%-------------------------------------------------------------------- %% Function: @@ -209,6 +216,14 @@ session_info(ConnectionPid) -> peer_certificate(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, peer_certificate). +%%-------------------------------------------------------------------- +%% Function: +%% +%% Description: +%%-------------------------------------------------------------------- +renegotiation(ConnectionPid) -> + sync_send_all_state_event(ConnectionPid, renegotiate). + %%==================================================================== %% ssl_connection_sup API %%==================================================================== @@ -224,7 +239,6 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> gen_fsm:start_link(?MODULE, [Role, Host, Port, Socket, Options, User, CbInfo], []). - %%==================================================================== %% gen_fsm callbacks %%==================================================================== @@ -243,12 +257,13 @@ init([Role, Host, Port, Socket, {SSLOpts, _} = Options, Hashes0 = ssl_handshake:init_hashes(), try ssl_init(SSLOpts, Role) of - {ok, Ref, CacheRef, OwnCert, Key} -> + {ok, Ref, CacheRef, OwnCert, Key, DHParams} -> State = State0#state{tls_handshake_hashes = Hashes0, own_cert = OwnCert, cert_db_ref = Ref, session_cache = CacheRef, - private_key = Key}, + private_key = Key, + diffie_hellman_params = DHParams}, {ok, hello, State} catch throw:Error -> @@ -261,18 +276,20 @@ init([Role, Host, Port, Socket, {SSLOpts, _} = Options, %% {next_state, NextStateName, %% NextState, Timeout} | %% {stop, Reason, NewState} -%% Description:There should be one instance of this function for each possible -%% state name. Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_event/2, the instance of this function with the same name as -%% the current state name StateName is called to handle the event. It is also -%% called if a timeout occurs. +%% +%% Description:There should be one instance of this function for each +%% possible state name. Whenever a gen_fsm receives an event sent +%% using gen_fsm:send_event/2, the instance of this function with the +%% same name as the current state name StateName is called to handle +%% the event. It is also called if a timeout occurs. %%-------------------------------------------------------------------- hello(socket_control, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates} = State0) -> - Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts), + Hello = ssl_handshake:client_hello(Host, Port, + ConnectionStates, SslOpts), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), {BinMsg, CS2, Hashes1} = @@ -289,7 +306,7 @@ hello(socket_control, #state{host = Host, port = Port, role = client, hello(socket_control, #state{role = server} = State) -> {next_state, hello, next_record(State)}; -hello(hello, #state{role = client} = State) -> +hello(#hello_request{}, #state{role = client} = State) -> {next_state, hello, State}; hello(#server_hello{cipher_suite = CipherSuite, @@ -301,13 +318,14 @@ hello(#server_hello{cipher_suite = CipherSuite, host = Host, port = Port, session_cache = Cache, session_cache_cb = CacheCb} = State0) -> + {Version, NewId, ConnectionStates1} = ssl_handshake:hello(Hello, ConnectionStates0), {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - PremasterSecret = make_premaster_secret(ReqVersion), + PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), State = State0#state{key_algorithm = KeyAlgorithm, negotiated_version = Version, @@ -358,47 +376,45 @@ hello(Hello = #client_hello{client_version = ClientVersion}, abbreviated(socket_control, #state{role = server} = State) -> {next_state, abbreviated, State}; -abbreviated(hello, State) -> +abbreviated(#hello_request{}, State) -> {next_state, certify, State}; abbreviated(Finished = #finished{}, #state{role = server, negotiated_version = Version, tls_handshake_hashes = Hashes, - session = #session{master_secret = MasterSecret}, - from = From} = State) -> + session = #session{master_secret = MasterSecret}} = + State0) -> case ssl_handshake:verify_connection(Version, Finished, client, MasterSecret, Hashes) of verified -> - gen_fsm:reply(From, connected), - {next_state, connection, next_record_if_active(State)}; - #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State), - {stop, normal, State} + State = ack_connection(State0), + next_state_connection(State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, abbreviated, State0), + {stop, normal, State0} end; abbreviated(Finished = #finished{}, #state{role = client, tls_handshake_hashes = Hashes0, session = #session{master_secret = MasterSecret}, - from = From, - negotiated_version = Version} = State) -> + negotiated_version = Version} = State0) -> case ssl_handshake:verify_connection(Version, Finished, server, MasterSecret, Hashes0) of verified -> - {ConnectionStates, Hashes} = finalize_client_handshake(State), - gen_fsm:reply(From, connected), - {next_state, connection, - next_record_if_active(State#state{tls_handshake_hashes = Hashes, - connection_states = - ConnectionStates})}; + {ConnectionStates, Hashes} = finalize_client_handshake(State0), + State = ack_connection(State0), + next_state_connection(State#state{tls_handshake_hashes = Hashes, + connection_states = + ConnectionStates}); #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State), - {stop, normal, State} + handle_own_alert(Alert, Version, abbreviated, State0), + {stop, normal, State0} end. certify(socket_control, #state{role = server} = State) -> {next_state, certify, State}; -certify(hello, State) -> +certify(#hello_request{}, State) -> {next_state, certify, State}; certify(#certificate{asn1_certificates = []}, @@ -415,52 +431,71 @@ certify(#certificate{asn1_certificates = []}, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = State) -> - {next_state, certify, next_record(State#state{client_certificate_requested = false})}; + {next_state, certify, + next_record(State#state{client_certificate_requested = false})}; certify(#certificate{} = Cert, - #state{session = Session, - negotiated_version = Version, + #state{negotiated_version = Version, + role = Role, cert_db_ref = CertDbRef, - ssl_options = Opts} = State0) -> + ssl_options = Opts} = State) -> case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth, Opts#ssl_options.verify, - Opts#ssl_options.verify_fun) of + Opts#ssl_options.verify_fun, + Opts#ssl_options.validate_extensions_fun, Role) of {PeerCert, PublicKeyInfo} -> - State = State0#state{session = - Session#session{peer_certificate = PeerCert}, - public_key_info = PublicKeyInfo, - client_certificate_requested = false - }, - {next_state, certify, next_record(State)}; + handle_peer_cert(PeerCert, PublicKeyInfo, + State#state{client_certificate_requested = false}); #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_certificate, State0), - {stop, normal, State0} + handle_own_alert(Alert, Version, certify_certificate, State), + {stop, normal, State} end; certify(#server_key_exchange{} = KeyExchangeMsg, - #state{role = client, - key_algorithm = Alg} = State) - when Alg == dhe_dss; Alg == dhe_rsa; Alg == dh_anon; Alg == krb5 -> - NewState = handle_server_key(KeyExchangeMsg, State), - {next_state, certify, NewState}; + #state{role = client, negotiated_version = Version, + key_algorithm = Alg} = State0) + when Alg == dhe_dss; Alg == dhe_rsa ->%%Not imp:Alg == dh_anon;Alg == krb5 -> + case handle_server_key(KeyExchangeMsg, State0) of + #state{} = State -> + {next_state, certify, next_record(State)}; + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify_server_keyexchange, + State0), + {stop, normal, State0} + end; certify(#server_key_exchange{}, State = #state{role = client, negotiated_version = Version, key_algorithm = Alg}) - when Alg == rsa; Alg == dh_dss; Alg == dh_rsa -> + when Alg == rsa; Alg == dh_dss; Alg == dh_rsa -> Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE), handle_own_alert(Alert, Version, certify_server_key_exchange, State), {stop, normal, State}; -certify(KeyExchangeMsg = #server_key_exchange{}, State = - #state{role = server}) -> - NewState = handle_clinet_key(KeyExchangeMsg, State), - {next_state, cipher, NewState}; - certify(#certificate_request{}, State) -> NewState = State#state{client_certificate_requested = true}, {next_state, certify, next_record(NewState)}; + +%% Master secret was determined with help of server-key exchange msg +certify(#server_hello_done{}, + #state{session = #session{master_secret = MasterSecret} = Session, + connection_states = ConnectionStates0, + negotiated_version = Version, + premaster_secret = undefined, + role = client} = State0) -> + case ssl_handshake:master_secret(Version, Session, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates1} -> + State = State0#state{connection_states = ConnectionStates1}, + client_certify_and_key_exchange(State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, + certify_server_hello_done, State0), + {stop, normal, State0} + end; + +%% Master secret is calculated from premaster_secret certify(#server_hello_done{}, #state{session = Session0, connection_states = ConnectionStates0, @@ -487,7 +522,8 @@ certify(#client_key_exchange{}, negotiated_version = Version}) -> %% We expect a certificate here Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE), - handle_own_alert(Alert, Version, certify_server_waiting_certificate, State), + handle_own_alert(Alert, Version, + certify_server_waiting_certificate, State), {stop, normal, State}; @@ -517,11 +553,41 @@ certify(#client_key_exchange{exchange_keys handle_own_alert(Alert, Version, certify_client_key_exchange, State0), {stop, normal, State0} + end; + +certify(#client_key_exchange{exchange_keys = #client_diffie_hellman_public{ + dh_public = ClientPublicDhKey}}, + #state{negotiated_version = Version, + diffie_hellman_params = #'DHParameter'{prime = P, + base = G}, + diffie_hellman_keys = {_, ServerDhPrivateKey}, + role = Role, + session = Session, + connection_states = ConnectionStates0} = State0) -> + + PMpint = crypto:mpint(P), + GMpint = crypto:mpint(G), + PremasterSecret = crypto:dh_compute_key(mpint_binary(ClientPublicDhKey), + ServerDhPrivateKey, + [PMpint, GMpint]), + + case ssl_handshake:master_secret(Version, PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State = State0#state{session = + Session#session{master_secret + = MasterSecret}, + connection_states = ConnectionStates}, + {next_state, cipher, next_record(State)}; + #alert{} = Alert -> + handle_own_alert(Alert, Version, + certify_client_key_exchange, State0), + {stop, normal, State0} end. cipher(socket_control, #state{role = server} = State) -> {next_state, cipher, State}; -cipher(hello, State) -> +cipher(#hello_request{}, State) -> {next_state, cipher, State}; cipher(#certificate_verify{signature = Signature}, @@ -543,43 +609,41 @@ cipher(#certificate_verify{signature = Signature}, end; cipher(#finished{} = Finished, - State = #state{from = From, - negotiated_version = Version, - host = Host, - port = Port, - role = Role, - session = #session{master_secret = MasterSecret} - = Session0, - tls_handshake_hashes = Hashes}) -> - + #state{negotiated_version = Version, + host = Host, + port = Port, + role = Role, + session = #session{master_secret = MasterSecret} + = Session0, + tls_handshake_hashes = Hashes} = State0) -> + case ssl_handshake:verify_connection(Version, Finished, opposite_role(Role), MasterSecret, Hashes) of verified -> - gen_fsm:reply(From, connected), + State = ack_connection(State0), Session = register_session(Role, Host, Port, Session0), case Role of client -> - {next_state, connection, - next_record_if_active(State#state{session = Session})}; + next_state_connection(State#state{session = Session}); server -> {NewConnectionStates, NewHashes} = finalize_server_handshake(State#state{ session = Session}), - NewState = - State#state{connection_states = NewConnectionStates, - session = Session, - tls_handshake_hashes = NewHashes}, - {next_state, connection, next_record_if_active(NewState)} + next_state_connection(State#state{connection_states = + NewConnectionStates, + session = Session, + tls_handshake_hashes = + NewHashes}) end; #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State), - {stop, normal, State} + handle_own_alert(Alert, Version, cipher, State0), + {stop, normal, State0} end. connection(socket_control, #state{role = server} = State) -> {next_state, connection, State}; -connection(hello, State = #state{host = Host, port = Port, +connection(#hello_request{}, State = #state{host = Host, port = Port, socket = Socket, ssl_options = SslOpts, negotiated_version = Version, @@ -592,42 +656,11 @@ connection(hello, State = #state{host = Host, port = Port, {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - {next_state, hello, State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}}. - -%%-------------------------------------------------------------------- -%% Function: -%% state_name(Event, From, State) -> {next_state, NextStateName, NextState} | -%% {next_state, NextStateName, -%% NextState, Timeout} | -%% {reply, Reply, NextStateName, NextState}| -%% {reply, Reply, NextStateName, -%% NextState, Timeout} | -%% {stop, Reason, NewState}| -%% {stop, Reason, Reply, NewState} -%% Description: There should be one instance of this function for each -%% possible state name. Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_event/2,3, the instance of this function with the same -%% name as the current state name StateName is called to handle the event. -%%-------------------------------------------------------------------- -connection({application_data, Data0}, _From, - State = #state{socket = Socket, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0}) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - Data = encode_packet(Data0, State#state.socket_options), - {Msgs, ConnectionStates1} = encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - {reply, Result, - connection, State#state{connection_states = ConnectionStates1}} - - catch throw:Error -> - {reply, Error, connection, State} - end. + {next_state, hello, next_record(State#state{connection_states = + ConnectionStates1, + tls_handshake_hashes = Hashes1})}; +connection(#client_hello{} = Hello, #state{role = server} = State) -> + hello(Hello, State). %%-------------------------------------------------------------------- %% Function: @@ -642,22 +675,37 @@ connection({application_data, Data0}, _From, %%-------------------------------------------------------------------- handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, - State = #state{key_algorithm = KeyAlg, - tls_handshake_buffer = Buf0, - negotiated_version = Version}) -> + State0 = #state{key_algorithm = KeyAlg, + tls_handshake_buffer = Buf0, + negotiated_version = Version}) -> Handle = - fun({Packet, Raw}, {next_state, SName, AS=#state{tls_handshake_hashes=Hs0}}) -> + fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Starts new handshake (renegotiation) + Hs0 = ssl_handshake:init_hashes(), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0, + renegotiation = {true, peer}}); + ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Already in negotiation so it will be ignored! + ?MODULE:SName(Packet, State); + ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> + Hs0 = ssl_handshake:init_hashes(), + Hs1 = ssl_handshake:update_hashes(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1, + renegotiation = {true, peer}}); + ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) -> Hs1 = ssl_handshake:update_hashes(Hs0, Raw), - ?MODULE:SName(Packet, AS#state{tls_handshake_hashes=Hs1}); + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1}); (_, StopState) -> StopState end, try {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), - Start = {next_state, StateName, State#state{tls_handshake_buffer = Buf}}, + Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}}, lists:foldl(Handle, Start, Packets) catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State), - {stop, normal, State} + handle_own_alert(Alert, Version, StateName, State0), + {stop, normal, State0} end; handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, @@ -686,7 +734,8 @@ handle_event(#ssl_tls{type = ?ALERT, fragment = Data}, StateName, State) -> {next_state, StateName, State}; handle_event(#alert{level = ?FATAL} = Alert, connection, - #state{from = From, user_application = {_Mon, Pid}, log_alert = Log, + #state{from = From, user_application = {_Mon, Pid}, + log_alert = Log, host = Host, port = Port, session = Session, role = Role, socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), @@ -712,11 +761,21 @@ handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, _, #state{from = From, role = Role} = State) -> alert_user(From, Alert, Role), {stop, normal, State}; -handle_event(#alert{level = ?WARNING} = Alert, StateName, + +handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{log_alert = Log, renegotiation = {true, internal}} = State) -> + log_alert(Log, StateName, Alert), + {stop, normal, State}; + +handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{log_alert = Log, renegotiation = {true, From}} = State) -> + log_alert(Log, StateName, Alert), + gen_fsm:reply(From, {error, renegotiation_rejected}), + {next_state, connection, next_record(State)}; + +handle_event(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName, #state{log_alert = Log} = State) -> log_alert(Log, StateName, Alert), -%%TODO: Could be user_canceled or no_negotiation should the latter be - %% treated as fatal?! {next_state, StateName, next_record(State)}. %%-------------------------------------------------------------------- @@ -734,6 +793,43 @@ handle_event(#alert{level = ?WARNING} = Alert, StateName, %% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle %% the event. %%-------------------------------------------------------------------- +handle_sync_event({application_data, Data0}, From, connection, + #state{socket = Socket, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + send_queue = SendQueue, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} + = State) -> + %% We should look into having a worker process to do this to + %% parallize send and receive decoding and not block the receiver + %% if sending is overloading the socket. + try + Data = encode_packet(Data0, SockOpts), + case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of + {Msgs, [], ConnectionStates} -> + Result = Transport:send(Socket, Msgs), + {reply, Result, + connection, State#state{connection_states = ConnectionStates}}; + {Msgs, RestData, ConnectionStates} -> + if + Msgs =/= [] -> + Transport:send(Socket, Msgs); + true -> + ok + end, + renegotiate(State#state{connection_states = ConnectionStates, + send_queue = queue:in_r({From, RestData}, SendQueue), + renegotiation = {true, internal}}) + end + catch throw:Error -> + {reply, Error, connection, State} + end; +handle_sync_event({application_data, Data}, From, StateName, + #state{send_queue = Queue} = State) -> + %% In renegotiation priorities handshake, send data when handshake is finished + {next_state, StateName, State#state{send_queue = queue:in({From, Data}, Queue)}}; handle_sync_event(started, From, StateName, State) -> {next_state, StateName, State#state{from = From}}; @@ -750,23 +846,14 @@ handle_sync_event({shutdown, How}, From, StateName, {stop, normal, Error, State#state{from = From}} end; -%% TODO: men vad g�r next_record om det �r t.ex. renegotiate? kanske -%% inte bra... t�l att t�nkas p�! -handle_sync_event({recv, N}, From, StateName, - State0 = #state{user_data_buffer = Buffer}) -> - State1 = State0#state{bytes_to_read = N, from = From}, - case Buffer of - <<>> -> - State = next_record(State1), - {next_state, StateName, State}; - _ -> - case application_data(<<>>, State1) of - Stop = {stop, _, _} -> - Stop; - State -> - {next_state, StateName, State} - end - end; +handle_sync_event({recv, N}, From, connection = StateName, State0) -> + passive_receive(State0#state{bytes_to_read = N, from = From}, StateName); + +%% Doing renegotiate wait with handling request until renegotiate is +%% finished. Will be handled by next_state_connection/1. +handle_sync_event({recv, N}, From, StateName, State) -> + {next_state, StateName, State#state{bytes_to_read = N, from = From, + recv_during_renegotiation = true}}; handle_sync_event({new_user, User}, _From, StateName, State =#state{user_application = {OldMon, _}}) -> @@ -814,6 +901,12 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, end end; +handle_sync_event(renegotiate, From, connection, State) -> + renegotiate(State#state{renegotiation = {true, From}}); + +handle_sync_event(renegotiate, _, StateName, State) -> + {reply, {error, already_renegotiating}, StateName, State}; + handle_sync_event(info, _, StateName, #state{negotiated_version = Version, session = #session{cipher_suite = Suite}} = State) -> @@ -834,7 +927,6 @@ handle_sync_event(peer_certificate, _, StateName, = State) -> {reply, {ok, Cert}, StateName, State}. - %%-------------------------------------------------------------------- %% Function: %% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}| @@ -863,34 +955,6 @@ handle_info({Protocol, _, Data}, StateName, State = {stop, normal, State} end; -%% %% This is the code for {packet,ssl} removed because it was slower -%% %% than handling it in erlang. -%% handle_info(Data = #ssl_tls{}, StateName, -%% State = #state{tls_buffer = Buffer, -%% socket = Socket, -%% connection_states = ConnectionStates0}) -> -%% case Buffer of -%% buffer -> -%% {next_state, StateName, State#state{tls_buffer = [Data]}}; -%% continue -> -%% inet:setopts(Socket, [{active,once}]), -%% {Plain, ConnectionStates} = -%% ssl_record:decode_cipher_text(Data, ConnectionStates0), -%% gen_fsm:send_all_state_event(self(), Plain), -%% {next_state, StateName, -%% State#state{tls_buffer = buffer, -%% connection_states = ConnectionStates}}; -%% List when is_list(List) -> -%% {next_state, StateName, -%% State#state{tls_buffer = Buffer ++ [Data]}} -%% end; - -%% handle_info(CloseMsg = {_, Socket}, StateName0, -%% #state{socket = Socket,tls_buffer = [Msg]} = State0) -> -%% %% Hmm we have a ssl_tls msg buffered, handle that first -%% %% and it proberbly is a close alert -%% {next_state, StateName0, State0#state{tls_buffer=[Msg,{ssl_close,CloseMsg}]}}; - handle_info({CloseTag, Socket}, _StateName, #state{socket = Socket, close_tag = CloseTag, negotiated_version = Version, host = Host, @@ -924,17 +988,23 @@ handle_info(A, StateName, State) -> %% necessary cleaning up. When it returns, the gen_fsm terminates with %% Reason. The return value is ignored. %%-------------------------------------------------------------------- -terminate(_Reason, connection, _S=#state{negotiated_version = Version, +terminate(_Reason, connection, #state{negotiated_version = Version, connection_states = ConnectionStates, transport_cb = Transport, - socket = Socket}) -> + socket = Socket, send_queue = SendQueue, + renegotiation = Renegotiate}) -> + notify_senders(SendQueue), + notify_renegotiater(Renegotiate), {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY), Version, ConnectionStates), Transport:send(Socket, BinAlert), Transport:close(Socket); -terminate(_Reason, _StateName, _S=#state{transport_cb = Transport, socket = Socket}) -> - Transport:close(Socket), - ok. +terminate(_Reason, _StateName, #state{transport_cb = Transport, + socket = Socket, send_queue = SendQueue, + renegotiation = Renegotiate}) -> + notify_senders(SendQueue), + notify_renegotiater(Renegotiate), + Transport:close(Socket). %%-------------------------------------------------------------------- %% Function: @@ -969,8 +1039,8 @@ ssl_init(SslOpts, Role) -> PrivateKey = init_private_key(SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, SslOpts#ssl_options.password, Role), - ?DBG_TERM(PrivateKey), - {ok, CertDbRef, CacheRef, OwnCert, PrivateKey}. + DHParams = init_diffie_hellman(SslOpts#ssl_options.dhfile, Role), + {ok, CertDbRef, CacheRef, OwnCert, PrivateKey, DHParams}. init_certificates(#ssl_options{cacertfile = CACertFile, certfile = CertFile}, Role) -> @@ -1005,12 +1075,14 @@ init_certificates(CertDbRef, CacheRef, CertFile, server) -> catch _E:{badmatch, _R={error,_}} -> Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n", - [?LINE, _E,_R, CertFile, erlang:get_stacktrace()]), + [?LINE, _E,_R, CertFile, + erlang:get_stacktrace()]), error_logger:error_report(Report), throw(ecertfile); _E:_R -> Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n", - [?LINE, _E,_R, CertFile, erlang:get_stacktrace()]), + [?LINE, _E,_R, CertFile, + erlang:get_stacktrace()]), error_logger:error_report(Report), throw(ecertfile) end. @@ -1021,40 +1093,43 @@ init_private_key(undefined, KeyFile, Password, _) -> try {ok, List} = ssl_manager:cache_pem_file(KeyFile), [Der] = [Der || Der = {PKey, _ , _} <- List, - PKey =:= rsa_private_key orelse PKey =:= dsa_private_key], + PKey =:= rsa_private_key orelse + PKey =:= dsa_private_key], {ok, Decoded} = public_key:decode_private_key(Der,Password), Decoded catch _E:{badmatch, _R={error,_}} -> Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n", - [?LINE, _E,_R, KeyFile, erlang:get_stacktrace()]), + [?LINE, _E,_R, KeyFile, + erlang:get_stacktrace()]), error_logger:error_report(Report), throw(ekeyfile); _E:_R -> Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n", - [?LINE, _E,_R, KeyFile, erlang:get_stacktrace()]), + [?LINE, _E,_R, KeyFile, + erlang:get_stacktrace()]), error_logger:error_report(Report), throw(ekeyfile) end; init_private_key(PrivateKey, _, _,_) -> PrivateKey. -send_event(FsmPid, Event) -> - gen_fsm:send_event(FsmPid, Event). - -sync_send_event(FsmPid, Event, Timeout) -> - try gen_fsm:sync_send_event(FsmPid, Event, Timeout) of - Reply -> - Reply - catch - exit:{noproc, _} -> - {error, closed}; - exit:{timeout, _} -> - {error, timeout}; - exit:{normal, _} -> - {error, closed} +init_diffie_hellman(_, client) -> + undefined; +init_diffie_hellman(undefined, _) -> + ?DEFAULT_DIFFIE_HELLMAN_PARAMS; +init_diffie_hellman(DHParamFile, server) -> + {ok, List} = ssl_manager:cache_pem_file(DHParamFile), + case [Der || Der = {dh_params, _ , _} <- List] of + [Der] -> + {ok, Decoded} = public_key:decode_dhparams(Der), + Decoded; + [] -> + ?DEFAULT_DIFFIE_HELLMAN_PARAMS end. +send_event(FsmPid, Event) -> + gen_fsm:send_event(FsmPid, Event). send_all_state_event(FsmPid, Event) -> @@ -1078,6 +1153,16 @@ sync_send_all_state_event(FsmPid, Event, Timeout) -> alert_event(Alert) -> send_all_state_event(self(), Alert). +%% We do currently not support cipher suites that use fixed DH. +%% If we want to implement that we should add a code +%% here to extract DH parameters form cert. +handle_peer_cert(PeerCert, PublicKeyInfo, + #state{session = Session} = State0) -> + State = State0#state{session = + Session#session{peer_certificate = PeerCert}, + public_key_info = PublicKeyInfo}, + {next_state, certify, next_record(State)}. + certify_client(#state{client_certificate_requested = true, role = client, connection_states = ConnectionStates0, transport_cb = Transport, @@ -1111,9 +1196,8 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, ignore -> %% No key or cert or fixed_diffie_hellman State; Verified -> - SigAlg = ssl_handshake:sig_alg(KeyAlg), {BinVerified, ConnectionStates1, Hashes1} = - encode_handshake(Verified, SigAlg, Version, + encode_handshake(Verified, KeyAlg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinVerified), State#state{connection_states = ConnectionStates1, @@ -1140,8 +1224,10 @@ do_server_hello(Type, #state{negotiated_version = Version, {_, ConnectionStates1} -> State1 = State#state{connection_states=ConnectionStates1, session = Session}, - {ConnectionStates, Hashes} = finalize_server_handshake(State1), - Resumed = State1#state{connection_states = ConnectionStates, + {ConnectionStates, Hashes} = + finalize_server_handshake(State1), + Resumed = State1#state{connection_states = + ConnectionStates, tls_handshake_hashes = Hashes}, {next_state, abbreviated, next_record(Resumed)}; #alert{} = Alert -> @@ -1252,14 +1338,15 @@ key_exchange(#state{role = server, key_algorithm = Algo} = State) Algo == dh_rsa -> State; -key_exchange(#state{role = server, key_algorithm = rsa_export} = State) -> +%key_exchange(#state{role = server, key_algorithm = rsa_export} = State) -> %% TODO when the public key in the server certificate is %% less than or equal to 512 bits in length dont send key_exchange %% but do it otherwise - State; +% State; key_exchange(#state{role = server, key_algorithm = Algo, diffie_hellman_params = Params, + private_key = PrivateKey, connection_states = ConnectionStates0, negotiated_version = Version, tls_handshake_hashes = Hashes0, @@ -1270,26 +1357,28 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo == dhe_dss_export; Algo == dhe_rsa; Algo == dhe_rsa_export -> - Msg = ssl_handshake:key_exchange(server, Params), - {BinMsg, ConnectionStates1, Hashes1} = - encode_handshake(Msg, Version, ConnectionStates0, Hashes0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}; -key_exchange(#state{role = server, key_algorithm = dh_anon, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_hashes = Hashes0, - socket = Socket, - transport_cb = Transport - } = State) -> - Msg = ssl_handshake:key_exchange(server, anonymous), - {BinMsg, ConnectionStates1, Hashes1} = + Keys = public_key:gen_key(Params), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, {dh, Keys, Params, + Algo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Hashes1} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, + State#state{connection_states = ConnectionStates, + diffie_hellman_keys = Keys, tls_handshake_hashes = Hashes1}; + + +%% key_algorithm = dh_anon is not supported. Should be by default disabled +%% if support is implemented and then we need a key_exchange clause for it +%% here. key_exchange(#state{role = client, connection_states = ConnectionStates0, @@ -1309,17 +1398,33 @@ key_exchange(#state{role = client, key_exchange(#state{role = client, connection_states = ConnectionStates0, key_algorithm = Algorithm, - public_key_info = PublicKeyInfo, negotiated_version = Version, - diffie_hellman_params = Params, - own_cert = Cert, + diffie_hellman_keys = {DhPubKey, _}, socket = Socket, transport_cb = Transport, tls_handshake_hashes = Hashes0} = State) when Algorithm == dhe_dss; Algorithm == dhe_dss_export; Algorithm == dhe_rsa; Algorithm == dhe_rsa_export -> - Msg = dh_key_exchange(Cert, Params, PublicKeyInfo), + Msg = ssl_handshake:key_exchange(client, {dh, DhPubKey}), + {BinMsg, ConnectionStates1, Hashes1} = + encode_handshake(Msg, Version, ConnectionStates0, Hashes0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates1, + tls_handshake_hashes = Hashes1}; + +key_exchange(#state{role = client, + connection_states = ConnectionStates0, + key_algorithm = Algorithm, + negotiated_version = Version, + client_certificate_requested = ClientCertReq, + own_cert = OwnCert, + diffie_hellman_keys = DhKeys, + socket = Socket, transport_cb = Transport, + tls_handshake_hashes = Hashes0} = State) + when Algorithm == dh_dss; + Algorithm == dh_rsa -> + Msg = dh_key_exchange(OwnCert, DhKeys, ClientCertReq), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), @@ -1334,17 +1439,19 @@ rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) ssl_handshake:key_exchange(client, {premaster_secret, PremasterSecret, PublicKeyInfo}); - rsa_key_exchange(_, _) -> throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). -dh_key_exchange(OwnCert, Params, PublicKeyInfo) -> +dh_key_exchange(OwnCert, DhKeys, true) -> case public_key:pkix_is_fixed_dh_cert(OwnCert) of true -> ssl_handshake:key_exchange(client, fixed_diffie_hellman); false -> - ssl_handshake:key_exchange(client, {dh, Params, PublicKeyInfo}) - end. + {DhPubKey, _} = DhKeys, + ssl_handshake:key_exchange(client, {dh, DhPubKey}) + end; +dh_key_exchange(_, {DhPubKey, _}, false) -> + ssl_handshake:key_exchange(client, {dh, DhPubKey}). request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, connection_states = ConnectionStates0, @@ -1378,7 +1485,8 @@ finalize_client_handshake(#state{connection_states = ConnectionStates0} finalize_server_handshake(State) -> ConnectionStates0 = cipher_protocol(State), ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, write), + ssl_record:activate_pending_connection_state(ConnectionStates0, + write), finished(State#state{connection_states = ConnectionStates}). cipher_protocol(#state{connection_states = ConnectionStates, @@ -1386,7 +1494,8 @@ cipher_protocol(#state{connection_states = ConnectionStates, negotiated_version = Version, transport_cb = Transport}) -> {BinChangeCipher, NewConnectionStates} = - encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates), + encode_change_cipher(#change_cipher_spec{}, + Version, ConnectionStates), Transport:send(Socket, BinChangeCipher), NewConnectionStates. @@ -1402,10 +1511,66 @@ finished(#state{role = Role, socket = Socket, negotiated_version = Version, Transport:send(Socket, BinFinished), {NewConnectionStates, NewHashes}. -handle_server_key(_KeyExchangeMsg, State) -> - State. -handle_clinet_key(_KeyExchangeMsg, State) -> - State. +handle_server_key( + #server_key_exchange{params = + #server_dh_params{dh_p = P, + dh_g = G, + dh_y = ServerPublicDhKey}, + signed_params = Signed}, + #state{session = Session, negotiated_version = Version, role = Role, + public_key_info = PubKeyInfo, + key_algorithm = KeyAlgo, + connection_states = ConnectionStates0} = State) -> + + PLen = size(P), + GLen = size(G), + YLen = size(ServerPublicDhKey), + + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Hash = ssl_handshake:server_key_exchange_hash(KeyAlgo, + <<ClientRandom/binary, + ServerRandom/binary, + ?UINT16(PLen), P/binary, + ?UINT16(GLen), G/binary, + ?UINT16(YLen), + ServerPublicDhKey/binary>>), + + case verify_dh_params(Signed, Hash, PubKeyInfo) of + true -> + PMpint = mpint_binary(P), + GMpint = mpint_binary(G), + Keys = {_, ClientDhPrivateKey} = + crypto:dh_generate_key([PMpint,GMpint]), + PremasterSecret = + crypto:dh_compute_key(mpint_binary(ServerPublicDhKey), + ClientDhPrivateKey, [PMpint, GMpint]), + case ssl_handshake:master_secret(Version, PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State#state{diffie_hellman_keys = Keys, + session = + Session#session{master_secret + = MasterSecret}, + connection_states = ConnectionStates}; + #alert{} = Alert -> + Alert + end; + false -> + ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE) + end. + +verify_dh_params(Signed, Hash, {?rsaEncryption, PubKey, _PubKeyparams}) -> + case public_key:decrypt_public(Signed, PubKey, + [{rsa_pad, rsa_pkcs1_padding}]) of + Hash -> + true; + _ -> + false + end. encode_alert(#alert{} = Alert, Version, ConnectionStates) -> ?DBG_TERM(Alert), @@ -1442,8 +1607,8 @@ encode_size_packet(Bin, Size, Max) -> false -> <<Len:Size, Bin/binary>> end. -encode_data(Data, Version, ConnectionStates) -> - ssl_record:encode_data(Data, Version, ConnectionStates). +encode_data(Data, Version, ConnectionStates, RenegotiateAt) -> + ssl_record:encode_data(Data, Version, ConnectionStates, RenegotiateAt). decode_alerts(Bin) -> decode_alerts(Bin, []). @@ -1454,6 +1619,20 @@ decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) -> decode_alerts(<<>>, Acc) -> lists:reverse(Acc, []). +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> + case Buffer of + <<>> -> + State = next_record(State0), + {next_state, StateName, State}; + _ -> + case application_data(<<>>, State0) of + Stop = {stop, _, _} -> + Stop; + State -> + {next_state, StateName, State} + end + end. + application_data(Data, #state{user_application = {_Mon, Pid}, socket_options = SOpts, bytes_to_read = BytesToRead, @@ -1504,19 +1683,49 @@ get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) end; get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> PacketOpts = [{packet_size, Size}], - case erlang:decode_packet(Type, Buffer, PacketOpts) of + case decode_packet(Type, Buffer, PacketOpts) of {more, _} -> {ok, <<>>, Buffer}; Decoded -> Decoded end. -deliver_app_data(SO = #socket_options{active=once}, Data, Pid, From) -> - send_or_reply(once, Pid, From, format_reply(SO, Data)), - SO#socket_options{active=false}; -deliver_app_data(SO= #socket_options{active=Active}, Data, Pid, From) -> - send_or_reply(Active, Pid, From, format_reply(SO, Data)), - SO. +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data(SOpts = #socket_options{active=Active, packet=Type}, + Data, Pid, From) -> + send_or_reply(Active, Pid, From, format_reply(SOpts, Data)), + SO = case Data of + {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + % End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + _ -> + SO + end. format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) -> {ok, format_reply(Mode, Header, Data)}; @@ -1557,34 +1766,6 @@ opposite_role(server) -> send_user(Pid, Msg) -> Pid ! Msg. -%% %% This is the code for {packet,ssl} removed because it was slower -%% %% than handling it in erlang. -%% next_record(#state{socket = Socket, -%% tls_buffer = [Msg|Rest], -%% connection_states = ConnectionStates0} = State) -> -%% Buffer = -%% case Rest of -%% [] -> -%% inet:setopts(Socket, [{active,once}]), -%% buffer; -%% _ -> Rest -%% end, -%% case Msg of -%% #ssl_tls{} -> -%% {Plain, ConnectionStates} = -%% ssl_record:decode_cipher_text(Msg, ConnectionStates0), -%% gen_fsm:send_all_state_event(self(), Plain), -%% State#state{tls_buffer=Buffer, connection_states = ConnectionStates}; -%% {ssl_close, Msg} -> -%% self() ! Msg, -%% State#state{tls_buffer=Buffer} -%% end; -%% next_record(#state{socket = Socket, tls_buffer = undefined} = State) -> -%% inet:setopts(Socket, [{active,once}]), -%% State#state{tls_buffer=continue}; -%% next_record(State) -> -%% State#state{tls_buffer=continue}. - next_record(#state{tls_cipher_texts = [], socket = Socket} = State) -> inet:setopts(Socket, [{active,once}]), State; @@ -1594,13 +1775,57 @@ next_record(#state{tls_cipher_texts = [CT | Rest], gen_fsm:send_all_state_event(self(), Plain), State#state{tls_cipher_texts = Rest, connection_states = ConnStates}. + next_record_if_active(State = #state{socket_options = #socket_options{active = false}}) -> State; + next_record_if_active(State) -> next_record(State). +next_state_connection(#state{send_queue = Queue0, + negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} + } = State) -> + %% Send queued up data + case queue:out(Queue0) of + {{value, {From, Data}}, Queue} -> + case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of + {Msgs, [], ConnectionStates} -> + Result = Transport:send(Socket, Msgs), + gen_fsm:reply(From, Result), + next_state_connection(State#state{connection_states = ConnectionStates, + send_queue = Queue}); + %% This is unlikely to happen. User configuration of the + %% undocumented test option renegotiation_at can make it more likely. + {Msgs, RestData, ConnectionStates} -> + if + Msgs =/= [] -> + Transport:send(Socket, Msgs); + true -> + ok + end, + renegotiate(State#state{connection_states = ConnectionStates, + send_queue = queue:in_r({From, RestData}, Queue), + renegotiation = {true, internal}}) + end; + {empty, Queue0} -> + next_state_is_connection(State) + end. + +next_state_is_connection(State = + #state{recv_during_renegotiation = true, socket_options = + #socket_options{active = false}}) -> + passive_receive(State#state{recv_during_renegotiation = false}, connection); + +next_state_is_connection(State) -> + {next_state, connection, next_record_if_active(State)}. + + register_session(_, _, _, #session{is_resumable = true} = Session) -> Session; %% Already registered register_session(client, Host, Port, Session0) -> @@ -1650,7 +1875,10 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, bytes_to_read = 0, user_data_buffer = <<>>, log_alert = true, - session_cache_cb = SessionCacheCb + session_cache_cb = SessionCacheCb, + renegotiation = {false, first}, + recv_during_renegotiation = false, + send_queue = queue:new() }. sslsocket(Pid) -> @@ -1665,8 +1893,12 @@ get_socket_opts(Socket, [mode | Tags], SockOpts, Acc) -> get_socket_opts(Socket, Tags, SockOpts, [{mode, SockOpts#socket_options.mode} | Acc]); get_socket_opts(Socket, [packet | Tags], SockOpts, Acc) -> - get_socket_opts(Socket, Tags, SockOpts, - [{packet, SockOpts#socket_options.packet} | Acc]); + case SockOpts#socket_options.packet of + {Type, headers} -> + get_socket_opts(Socket, Tags, SockOpts, [{packet, Type} | Acc]); + Type -> + get_socket_opts(Socket, Tags, SockOpts, [{packet, Type} | Acc]) + end; get_socket_opts(Socket, [header | Tags], SockOpts, Acc) -> get_socket_opts(Socket, Tags, SockOpts, [{header, SockOpts#socket_options.header} | Acc]); @@ -1688,7 +1920,8 @@ set_socket_opts(Socket, [], SockOpts, Other) -> inet:setopts(Socket, Other), SockOpts; set_socket_opts(Socket, [{mode, Mode}| Opts], SockOpts, Other) -> - set_socket_opts(Socket, Opts, SockOpts#socket_options{mode = Mode}, Other); + set_socket_opts(Socket, Opts, + SockOpts#socket_options{mode = Mode}, Other); set_socket_opts(Socket, [{packet, Packet}| Opts], SockOpts, Other) -> set_socket_opts(Socket, Opts, SockOpts#socket_options{packet = Packet}, Other); @@ -1743,7 +1976,55 @@ handle_own_alert(Alert, Version, StateName, catch _:_ -> ok end. - -make_premaster_secret({MajVer, MinVer}) -> +make_premaster_secret({MajVer, MinVer}, Alg) when Alg == rsa; + Alg == dh_dss; + Alg == dh_rsa -> Rand = crypto:rand_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>. + <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; +make_premaster_secret(_, _) -> + undefined. + +mpint_binary(Binary) -> + Size = byte_size(Binary), + <<?UINT32(Size), Binary/binary>>. + + +ack_connection(#state{renegotiation = {true, Initiater}} = State) + when Initiater == internal; + Initiater == peer -> + State#state{renegotiation = undefined}; +ack_connection(#state{renegotiation = {true, From}} = State) -> + gen_fsm:reply(From, ok), + State#state{renegotiation = undefined}; +ack_connection(#state{renegotiation = {false, first}, from = From} = State) -> + gen_fsm:reply(From, connected), + State#state{renegotiation = undefined}. + +renegotiate(#state{role = client} = State) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_hashes(), + connection(#hello_request{}, State#state{tls_handshake_hashes = Hs0}); +renegotiate(#state{role = server, + socket = Socket, + transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates0} = State) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = ssl_handshake:encode_handshake(HelloRequest, Version, undefined), + Hs0 = ssl_handshake:init_hashes(), + {BinMsg, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + {next_state, hello, next_record(State#state{connection_states = + ConnectionStates, + tls_handshake_hashes = Hs0})}. +notify_senders(SendQueue) -> + lists:foreach(fun({From, _}) -> + gen_fsm:reply(From, {error, closed}) + end, queue:to_list(SendQueue)). + +notify_renegotiater({true, From}) when not is_atom(From) -> + gen_fsm:reply(From, {error, closed}); +notify_renegotiater(_) -> + ok. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 8c598135ca..9f5ac7106a 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -31,11 +31,11 @@ -include("ssl_debug.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/4, server_hello/3, hello/2, - certify/5, certificate/3, +-export([master_secret/4, client_hello/4, server_hello/3, hello/2, + hello_request/0, certify/7, certificate/3, client_certificate_verify/6, certificate_verify/6, certificate_request/2, - key_exchange/2, finished/4, + key_exchange/2, server_key_exchange_hash/2, finished/4, verify_connection/5, get_tls_handshake/4, server_hello_done/0, sig_alg/1, @@ -97,6 +97,15 @@ server_hello(SessionId, Version, ConnectionStates) -> }. %%-------------------------------------------------------------------- +%% Function: hello_request() -> #hello_request{} +%% +%% Description: Creates a hello request message sent by server to +%% trigger renegotiation. +%%-------------------------------------------------------------------- +hello_request() -> + #hello_request{}. + +%%-------------------------------------------------------------------- %% Function: hello(Hello, Info) -> %% {Version, Id, NewConnectionStates} | %% #alert{} @@ -152,10 +161,25 @@ hello(#client_hello{client_version = ClientVersion, random = Random} = Hello, %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef, - MaxPathLen, Verify, VerifyFun) -> + MaxPathLen, Verify, VerifyFun, ValidateFun, Role) -> [PeerCert | _] = ASN1Certs, VerifyBool = verify_bool(Verify), - + + ValidateExtensionFun = + case ValidateFun of + undefined -> + fun(Extensions, ValidationState, Verify0, AccError) -> + ssl_certificate:validate_extensions(Extensions, ValidationState, + [], Verify0, AccError, Role) + end; + Fun -> + fun(Extensions, ValidationState, Verify0, AccError) -> + {NewExtensions, NewValidationState, NewAccError} + = ssl_certificate:validate_extensions(Extensions, ValidationState, + [], Verify0, AccError, Role), + Fun(NewExtensions, NewValidationState, Verify0, NewAccError) + end + end, try %% Allow missing root_cert and check that with VerifyFun ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef, false) of @@ -165,6 +189,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef, [{max_path_length, MaxPathLen}, {verify, VerifyBool}, + {validate_extensions_fun, + ValidateExtensionFun}, {acc_errors, VerifyErrors}]), case Result of @@ -248,8 +274,10 @@ client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm, %% Description: Checks that the certificate_verify message is valid. %%-------------------------------------------------------------------- certificate_verify(Signature, {_, PublicKey, _}, Version, - MasterSecret, Algorithm, {_, Hashes0}) - when Algorithm =:= rsa; Algorithm =:= dh_rsa; Algorithm =:= dhe_rsa -> + MasterSecret, Algorithm, {_, Hashes0}) + when Algorithm == rsa; + Algorithm == dh_rsa; + Algorithm == dhe_rsa -> Hashes = calc_certificate_verify(Version, MasterSecret, Algorithm, Hashes0), case public_key:decrypt_public(Signature, PublicKey, @@ -296,30 +324,32 @@ key_exchange(client, fixed_diffie_hellman) -> #client_diffie_hellman_public{ dh_public = <<>> }}; -key_exchange(client, {dh, PublicKey}) -> - Len = byte_size(PublicKey), +key_exchange(client, {dh, <<?UINT32(Len), PublicKey:Len/binary>>}) -> #client_key_exchange{ - exchange_keys = #client_diffie_hellman_public{ - dh_public = <<?UINT16(Len), PublicKey/binary>>} + exchange_keys = #client_diffie_hellman_public{ + dh_public = PublicKey} }; -%% key_exchange(server, {{?'dhpublicnumber', _PublicKey, -%% #'DomainParameters'{p = P, g = G, y = Y}, -%% SignAlgorithm, ClientRandom, ServerRandom}}) -> -%% ServerDHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, -%% PLen = byte_size(P), -%% GLen = byte_size(G), -%% YLen = byte_size(Y), -%% Hash = server_key_exchange_hash(SignAlgorithm, <<ClientRandom/binary, -%% ServerRandom/binary, -%% ?UINT16(PLen), P/binary, -%% ?UINT16(GLen), G/binary, -%% ?UINT16(YLen), Y/binary>>), -%% Signed = digitally_signed(Hash, PrivateKey), -%% #server_key_exchange{ -%% params = ServerDHParams, -%% signed_params = Signed -%% }; +key_exchange(server, {dh, {<<?UINT32(_), PublicKey/binary>>, _}, + #'DHParameter'{prime = P, base = G}, + KeyAlgo, ClientRandom, ServerRandom, PrivateKey}) -> + <<?UINT32(_), PBin/binary>> = crypto:mpint(P), + <<?UINT32(_), GBin/binary>> = crypto:mpint(G), + PLen = byte_size(PBin), + GLen = byte_size(GBin), + YLen = byte_size(PublicKey), + ServerDHParams = #server_dh_params{dh_p = PBin, + dh_g = GBin, dh_y = PublicKey}, + + Hash = + server_key_exchange_hash(KeyAlgo, <<ClientRandom/binary, + ServerRandom/binary, + ?UINT16(PLen), PBin/binary, + ?UINT16(GLen), GBin/binary, + ?UINT16(YLen), PublicKey/binary>>), + Signed = digitally_signed(Hash, PrivateKey), + #server_key_exchange{params = ServerDHParams, + signed_params = Signed}; key_exchange(_, _) -> %%TODO : Real imp #server_key_exchange{}. @@ -417,7 +447,8 @@ server_hello_done() -> %% %% encode a handshake packet to binary %%-------------------------------------------------------------------- -encode_handshake(Package, Version, SigAlg) -> +encode_handshake(Package, Version, KeyAlg) -> + SigAlg = sig_alg(KeyAlg), {MsgType, Bin} = enc_hs(Package, Version, SigAlg), Len = byte_size(Bin), [MsgType, ?uint24(Len), Bin]. @@ -437,32 +468,16 @@ get_tls_handshake(Data, Buffer, KeyAlg, Version) -> get_tls_handshake_aux(list_to_binary([Buffer, Data]), KeyAlg, Version, []). -get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length), Body:Length/binary,Rest/binary>>, - KeyAlg, Version, Acc) -> +get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length), + Body:Length/binary,Rest/binary>>, KeyAlg, + Version, Acc) -> Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, - H = dec_hs(Type, Body, KeyAlg, Version), + H = dec_hs(Type, Body, key_exchange_alg(KeyAlg), Version), get_tls_handshake_aux(Rest, KeyAlg, Version, [{H,Raw} | Acc]); get_tls_handshake_aux(Data, _KeyAlg, _Version, Acc) -> {lists:reverse(Acc), Data}. %%-------------------------------------------------------------------- -%% Function: sig_alg(atom()) -> integer() -%% -%% Description: Convert from key exchange as atom to signature -%% algorithm as a ?SIGNATURE_... constant -%%-------------------------------------------------------------------- - -sig_alg(dh_anon) -> - ?SIGNATURE_ANONYMOUS; -sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa; Alg == dh_rsa -> - ?SIGNATURE_RSA; -sig_alg(Alg) when Alg == dh_dss; Alg == dhe_dss -> - ?SIGNATURE_DSA; -sig_alg(_) -> - ?NULL. - - -%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- verify_bool(verify_peer) -> @@ -649,8 +664,8 @@ dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary, - ?UINT16(ExpLen), Exp:ExpLen/binary, - Sig/binary>>, + ?UINT16(ExpLen), Exp:ExpLen/binary, + ?UINT16(_), Sig/binary>>, ?KEY_EXCHANGE_RSA, _) -> #server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod, rsa_exponent = Exp}, @@ -658,9 +673,10 @@ dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary, dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary, ?UINT16(GLen), G:GLen/binary, ?UINT16(YLen), Y:YLen/binary, - Sig/binary>>, + ?UINT16(_), Sig/binary>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - #server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G, dh_y = Y}, + #server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G, + dh_y = Y}, signed_params = Sig}; dec_hs(?CERTIFICATE_REQUEST, <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, @@ -672,18 +688,20 @@ dec_hs(?SERVER_HELLO_DONE, <<>>, _, _) -> #server_hello_done{}; dec_hs(?CERTIFICATE_VERIFY,<<?UINT16(_), Signature/binary>>, _, _)-> #certificate_verify{signature = Signature}; -dec_hs(?CLIENT_KEY_EXCHANGE, PKEPMS, rsa, {3, 0}) -> +dec_hs(?CLIENT_KEY_EXCHANGE, PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS}, #client_key_exchange{exchange_keys = PreSecret}; -dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(_), PKEPMS/binary>>, rsa, _) -> +dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(_), PKEPMS/binary>>, + ?KEY_EXCHANGE_RSA, _) -> PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS}, #client_key_exchange{exchange_keys = PreSecret}; dec_hs(?CLIENT_KEY_EXCHANGE, <<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> %% TODO: Should check whether the cert already contains a suitable DH-key (7.4.7.2) throw(?ALERT_REC(?FATAL, implicit_public_value_encoding)); -dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(DH_YCLen), DH_YC:DH_YCLen/binary>>, +dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - #client_diffie_hellman_public{dh_public = DH_YC}; + #client_key_exchange{exchange_keys = + #client_diffie_hellman_public{dh_public = DH_Y}}; dec_hs(?FINISHED, VerifyData, _, _) -> #finished{verify_data = VerifyData}; dec_hs(_, _, _, _) -> @@ -756,12 +774,13 @@ enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) -> {?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>}; enc_hs(#server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod, rsa_exponent = Exp}, - signed_params = SignedParams}, _Version, _) -> + signed_params = SignedParams}, _Version, _) -> ModLen = byte_size(Mod), ExpLen = byte_size(Exp), - {?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod/binary, + SignedLen = byte_size(SignedParams), + {?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen),Mod/binary, ?UINT16(ExpLen), Exp/binary, - SignedParams/binary>> + ?UINT16(SignedLen), SignedParams/binary>> }; enc_hs(#server_key_exchange{params = #server_dh_params{ dh_p = P, dh_g = G, dh_y = Y}, @@ -769,10 +788,11 @@ enc_hs(#server_key_exchange{params = #server_dh_params{ PLen = byte_size(P), GLen = byte_size(G), YLen = byte_size(Y), - {?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?UINT16(YLen), Y:YLen/binary, - SignedParams/binary>> + SignedLen = byte_size(SignedParams), + {?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P/binary, + ?UINT16(GLen), G/binary, + ?UINT16(YLen), Y/binary, + ?UINT16(SignedLen), SignedParams/binary>> }; enc_hs(#certificate_request{certificate_types = CertTypes, certificate_authorities = CertAuths}, @@ -927,13 +947,40 @@ calc_certificate_verify({3, N}, _, Algorithm, Hashes) when N == 1; N == 2 -> ssl_tls1:certificate_verify(Algorithm, Hashes). -%% server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa; -%% Algorithm == dh_rsa; -%% Algorithm == dhe_rsa -> -%% MD5 = crypto:md5_final(Value), -%% SHA = crypto:sha_final(Value), -%% <<MD5/binary, SHA/binary>>; +server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa; + Algorithm == dh_rsa; + Algorithm == dhe_rsa -> + MD5Context = crypto:md5_init(), + NewMD5Context = crypto:md5_update(MD5Context, Value), + MD5 = crypto:md5_final(NewMD5Context), + + SHAContext = crypto:sha_init(), + NewSHAContext = crypto:sha_update(SHAContext, Value), + SHA = crypto:sha_final(NewSHAContext), + + <<MD5/binary, SHA/binary>>; + +server_key_exchange_hash(Algorithm, Value) when Algorithm == dh_dss; + Algorithm == dhe_dss -> + + SHAContext = crypto:sha_init(), + NewSHAContext = crypto:sha_update(SHAContext, Value), + crypto:sha_final(NewSHAContext). -%% server_key_exchange_hash(Algorithm, Value) when Algorithm == dh_dss; -%% Algorithm == dhe_dss -> -%% crypto:sha_final(Value). + +sig_alg(dh_anon) -> + ?SIGNATURE_ANONYMOUS; +sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa; Alg == dh_rsa -> + ?SIGNATURE_RSA; +sig_alg(Alg) when Alg == dh_dss; Alg == dhe_dss -> + ?SIGNATURE_DSA; +sig_alg(_) -> + ?NULL. + +key_exchange_alg(rsa) -> + ?KEY_EXCHANGE_RSA; +key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; + Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> + ?KEY_EXCHANGE_DIFFIE_HELLMAN; +key_exchange_alg(_) -> + ?NULL. diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index b2bdfa0934..889d39f2af 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -38,7 +38,8 @@ -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 -define(NUM_OF_PREMASTERSECRET_BYTES, 48). - +-define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2). +-define(DEFAULT_DIFFIE_HELLMAN_PRIME, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Handsake protocol - RFC 4346 section 7.4 diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 23a5c93452..8d19abfe1e 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -57,12 +57,15 @@ verify_fun, % fun(CertVerifyErrors) -> boolean() fail_if_no_peer_cert, % boolean() verify_client_once, % boolean() + %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError} + validate_extensions_fun, depth, % integer() certfile, % file() keyfile, % file() key, % password, % cacertfile, % file() + dhfile, % file() ciphers, % %% Local policy for the server if it want's to reuse the session %% or not. Defaluts to allways returning true. @@ -71,6 +74,7 @@ %% If false sessions will never be reused, if true they %% will be reused if possible. reuse_sessions, % boolean() + renegotiate_at, debug % }). diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 37a3d1b639..da48f049f6 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -45,7 +45,7 @@ %% Encoding records -export([encode_handshake/3, encode_alert_record/3, - encode_change_cipher_spec/2, encode_data/3]). + encode_change_cipher_spec/2, encode_data/4]). %% Decoding -export([decode_cipher_text/2]). @@ -474,19 +474,31 @@ split_bin(Bin, ChunkSize, Acc) -> lists:reverse(Acc, [Bin]) end. -encode_data(Frag, Version, ConnectionStates) +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) -> - encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates); -encode_data(Frag, Version, ConnectionStates) -> + case encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates, RenegotiateAt) of + {renegotiate, Data} -> + {[], Data, ConnectionStates}; + {Msg, CS} -> + {Msg, [], CS} + end; + +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when is_binary(Frag) -> Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048), - {CS1, Acc} = - lists:foldl(fun(B, {CS0, Acc}) -> - {ET, CS1} = - encode_plain_text(?APPLICATION_DATA, - Version, B, CS0), - {CS1, [ET | Acc]} - end, {ConnectionStates, []}, Data), - {lists:reverse(Acc), CS1}. + encode_data(Data, Version, ConnectionStates, RenegotiateAt); + +encode_data(Data, Version, ConnectionStates0, RenegotiateAt) when is_list(Data) -> + {ConnectionStates, EncodedMsg, NotEncdedData} = + lists:foldl(fun(B, {CS0, Encoded, Rest}) -> + case encode_plain_text(?APPLICATION_DATA, + Version, B, CS0, RenegotiateAt) of + {renegotiate, NotEnc} -> + {CS0, Encoded, [NotEnc | Rest]}; + {Enc, CS1} -> + {CS1, [Enc | Encoded], Rest} + end + end, {ConnectionStates0, [], []}, Data), + {lists:reverse(EncodedMsg), lists:reverse(NotEncdedData), ConnectionStates}. encode_handshake(Frag, Version, ConnectionStates) -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). @@ -499,6 +511,16 @@ encode_alert_record(#alert{level = Level, description = Description}, encode_change_cipher_spec(Version, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). +encode_plain_text(Type, Version, Data, ConnectionStates, RenegotiateAt) -> + #connection_states{current_write = + #connection_state{sequence_number = Num}} = ConnectionStates, + case renegotiate(Num, RenegotiateAt) of + false -> + encode_plain_text(Type, Version, Data, ConnectionStates); + true -> + {renegotiate, Data} + end. + encode_plain_text(Type, Version, Data, ConnectionStates) -> #connection_states{current_write=#connection_state{ compression_state=CompS0, @@ -511,6 +533,11 @@ encode_plain_text(Type, Version, Data, ConnectionStates) -> CTBin = encode_tls_cipher_text(Type, Version, CipherText), {CTBin, ConnectionStates#connection_states{current_write = CS2}}. +renegotiate(N, M) when N < M-> + false; +renegotiate(_,_) -> + true. + encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> Length = erlang:iolist_size(Fragment), [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment]. @@ -529,9 +556,6 @@ cipher(Type, Version, Fragment, CS0) -> CS2 = CS1#connection_state{cipher_state=CipherS1}, {Ciphered, CS2}. -decipher(TLS=#ssl_tls{type = ?CHANGE_CIPHER_SPEC}, CS) -> - %% These are never encrypted - {TLS, CS}; decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> SP = CS0#connection_state.security_parameters, BCA = SP#security_parameters.bulk_cipher_algorithm, % eller Cipher? diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 7370e0f0b3..362b7039d4 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -63,6 +63,13 @@ sequence_number }). +-define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 +%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. +%% We will renegotiate a little before so that there will be sequence numbers left +%% for the rehandshake and a little data. +-define(MARGIN, 100). +-define(DEFAULT_RENEGOTIATE_AT, ?MAX_SEQENCE_NUMBER - ?MARGIN). + %% ConnectionEnd -define(SERVER, 0). -define(CLIENT, 1). diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl index ab29ac64df..df809ce275 100644 --- a/lib/ssl/src/ssl_ssl3.erl +++ b/lib/ssl/src/ssl_ssl3.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -182,13 +182,13 @@ setup_keys(export, MasterSecret, ServerRandom, ClientRandom, suites() -> [ %% TODO: uncomment when supported - %% ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - %% TODO: Funkar inte, borde: ?TLS_RSA_WITH_AES_256_CBC_SHA, - %% ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - %% ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_AES_128_CBC_SHA, %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this? diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/ssl_tls1.erl index e0013c48ac..ce9a135168 100644 --- a/lib/ssl/src/ssl_tls1.erl +++ b/lib/ssl/src/ssl_tls1.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -136,13 +136,13 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, suites() -> [ %% TODO: uncomment when supported - %% ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - %% TODO: Funkar inte, borde: ?TLS_RSA_WITH_AES_256_CBC_SHA, - %% ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + %%?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - %% ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_AES_128_CBC_SHA, %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this? diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 3d9cec43dd..7f33efd7e1 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -26,10 +26,13 @@ -include("test_server.hrl"). -include("test_server_line.hrl"). +-include_lib("public_key/include/public_key.hrl"). -define('24H_in_sec', 86400). -define(TIMEOUT, 60000). -define(EXPIRE, 10). +-define(SLEEP, 500). + -behaviour(ssl_session_cache_api). @@ -150,19 +153,26 @@ all(doc) -> all(suite) -> [app, connection_info, controlling_process, controller_dies, peercert, connect_dist, - peername, sockname, socket_options, versions, cipher_suites, + peername, sockname, socket_options, misc_ssl_options, versions, cipher_suites, upgrade, upgrade_with_timeout, tcp_connect, ipv6, ekeyfile, ecertfile, ecacertfile, eoptions, shutdown, shutdown_write, shutdown_both, shutdown_error, ciphers, - send_close, + send_close, close_transport_accept, dh_params, server_verify_peer_passive, server_verify_peer_active, server_verify_peer_active_once, server_verify_none_passive, server_verify_none_active, - server_verify_none_active_once, - server_verify_no_cacerts, client_verify_none_passive, + server_verify_none_active_once, server_verify_no_cacerts, + server_require_peer_cert_ok, server_require_peer_cert_fail, + server_verify_client_once_passive, + server_verify_client_once_active, + server_verify_client_once_active_once, + client_verify_none_passive, client_verify_none_active, client_verify_none_active_once %%, session_cache_process_list, session_cache_process_mnesia - ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session + ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session, + client_renegotiate, server_renegotiate, + client_no_wrap_sequence_number, server_no_wrap_sequence_number, + extended_key_usage, validate_extensions_fun ]. %% Test cases starts here. @@ -236,7 +246,7 @@ controlling_process(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, + {from, self()}, {mfa, {?MODULE, controlling_process_result, [self(), ClientMsg]}}, @@ -267,7 +277,7 @@ controlling_process_result(Socket, Pid, Msg) -> ok = ssl:controlling_process(Socket, Pid), %% Make sure other side has evaluated controlling_process %% before message is sent - test_server:sleep(100), + test_server:sleep(?SLEEP), ssl:send(Socket, Msg), no_result_msg. @@ -298,7 +308,7 @@ controller_dies(Config) when is_list(Config) -> {options, ClientOpts}]), test_server:format("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - timer:sleep(200), %% so that they are connected + test_server:sleep(?SLEEP), %% so that they are connected process_flag(trap_exit, true), @@ -307,7 +317,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Client, ?LINE), %% Test that clients die when process disappear - Server ! listen, timer:sleep(200), + Server ! listen, Tester = self(), Connect = fun(Pid) -> {ok, Socket} = ssl:connect(Hostname, Port, @@ -321,7 +331,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Client2, ?LINE), %% Test that clients die when the controlling process have changed - Server ! listen, timer:sleep(200), + Server ! listen, Client3 = spawn_link(fun() -> Connect(Tester) end), Controller = spawn_link(fun() -> receive die_nice -> normal end end), @@ -345,7 +355,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Controller, ?LINE), %% Test that servers die - Server ! listen, timer:sleep(200), + Server ! listen, LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -353,7 +363,7 @@ controller_dies(Config) when is_list(Config) -> controller_dies_result, [self(), ClientMsg]}}, {options, [{reuseaddr,true}|ClientOpts]}]), - timer:sleep(200), %% so that they are connected + test_server:sleep(?SLEEP), %% so that they are connected exit(Server, killed), get_close(Server, ?LINE), @@ -484,9 +494,9 @@ peername(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peername_result, []}}, - {options, [{port, 0} | ClientOpts]}]), + {from, self()}, + {mfa, {?MODULE, peername_result, []}}, + {options, [{port, 0} | ClientOpts]}]), ClientPort = ssl_test_lib:inet_port(Client), ServerIp = ssl_test_lib:node_to_hostip(ServerNode), @@ -526,6 +536,7 @@ sockname(Config) when is_list(Config) -> {from, self()}, {mfa, {?MODULE, sockname_result, []}}, {options, [{port, 0} | ClientOpts]}]), + ClientPort = ssl_test_lib:inet_port(Client), ServerIp = ssl_test_lib:node_to_hostip(ServerNode), ClientIp = ssl_test_lib:node_to_hostip(ClientNode), @@ -602,6 +613,46 @@ socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> ok. %%-------------------------------------------------------------------- +misc_ssl_options(doc) -> + ["Test what happens when we give valid options"]; + +misc_ssl_options(suite) -> + []; + +misc_ssl_options(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% Chek that ssl options not tested elsewhere are filtered away e.i. not passed to inet. + TestOpts = [{depth, 1}, + {key, undefined}, + {password, []}, + {reuse_session, fun(_,_,_,_) -> true end}, + {debug, []}, + {cb_info, {gen_tcp, tcp, tcp_closed}}], + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, TestOpts ++ ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, TestOpts ++ ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- versions(doc) -> ["Test API function versions/0"]; @@ -667,13 +718,71 @@ send_close(Config) when is_list(Config) -> test_server:format("Testcase ~p, Client ~p Server ~p ~n", [self(), self(), Server]), - ok = ssl:send(SslS, "HejHopp"), - {ok,<<"Hejhopp">>} = ssl:recv(SslS, 7), + ok = ssl:send(SslS, "Hello world"), + {ok,<<"Hello world">>} = ssl:recv(SslS, 11), gen_tcp:close(TcpS), - {error, _} = ssl:send(SslS, "HejHopp"), + {error, _} = ssl:send(SslS, "Hello world"), ssl_test_lib:close(Server). %%-------------------------------------------------------------------- +close_transport_accept(doc) -> + ["Tests closing ssl socket when waiting on ssl:transport_accept/1"]; + +close_transport_accept(suite) -> + []; + +close_transport_accept(Config) when is_list(Config) -> + ServerOpts = ?config(server_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Port = 0, + Opts = [{active, false} | ServerOpts], + {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), + spawn_link(fun() -> + test_server:sleep(?SLEEP), + rpc:call(ServerNode, ssl, close, [ListenSocket]) + end), + case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of + {error, closed} -> + ok; + Other -> + exit({?LINE, Other}) + end. + +%%-------------------------------------------------------------------- +dh_params(doc) -> + ["Test to specify DH-params file in server."]; + +dh_params(suite) -> + []; + +dh_params(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + DataDir = ?config(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, [{dhfile, DHParamFile} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, + [{ciphers,[{dhe_rsa,aes_256_cbc,sha,ignore}]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- upgrade(doc) -> ["Test that you can upgrade an tcp connection to an ssl connection"]; @@ -710,11 +819,11 @@ upgrade(Config) when is_list(Config) -> ssl_test_lib:close(Client). upgrade_result(Socket) -> - ok = ssl:send(Socket, "Hejhopp"), + ok = ssl:send(Socket, "Hello world"), %% Make sure binary is inherited from tcp socket and that we do %% not get the list default! receive - {ssl, _, <<"Hejhopp">>} -> + {ssl, _, <<"Hello world">>} -> ok end. @@ -763,15 +872,14 @@ tcp_connect(suite) -> []; tcp_connect(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, {from, self()}, {timeout, 5000}, - {mfa, {?MODULE, should_close, []}}, + {mfa, {?MODULE, dummy, []}}, {tcp_options, TcpOpts}, {ssl_options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), @@ -780,18 +888,20 @@ tcp_connect(Config) when is_list(Config) -> test_server:format("Testcase ~p connected to Server ~p ~n", [self(), Server]), gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), - ssl_test_lib:check_result(Server, {error,esslerrssl}, tcp_closed, Socket), - + receive + {tcp_closed, Socket} -> + receive + {Server, {error, Error}} -> + test_server:format("Error ~p", [Error]) + end + end, ssl_test_lib:close(Server). -should_close(Socket) -> - receive - {ssl, Socket, closed} -> - server_closed; - Other -> - exit({?LINE, Other}) - end. +dummy(_Socket) -> + %% Should not happen as the ssl connection will not be established + %% due to fatal handshake failiure + exit(kill). %%-------------------------------------------------------------------- ipv6(doc) -> @@ -843,12 +953,14 @@ ekeyfile(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), BadOpts = ?config(server_bad_key, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), - + Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, BadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -869,19 +981,21 @@ ecertfile(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerBadOpts = ?config(server_bad_cert, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), - Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, ServerBadOpts}]), - Client0 = + + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {options, ClientOpts}]), - ssl_test_lib:check_result(Server0, {error, ecertfile}, Client0, + ssl_test_lib:check_result(Server, {error, ecertfile}, Client, {error, closed}). @@ -896,15 +1010,18 @@ ecacertfile(Config) when is_list(Config) -> ClientOpts = [{reuseaddr, true}|?config(client_opts, Config)], ServerBadOpts = [{reuseaddr, true}|?config(server_bad_ca, Config)], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), Server0 = ssl_test_lib:start_server_error([{node, ServerNode}, - {port, Port}, {from, self()}, + {port, 0}, {from, self()}, {options, ServerBadOpts}]), + + Port0 = ssl_test_lib:inet_port(Server0), + + Client0 = ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, Port0}, {host, Hostname}, {from, self()}, {options, ClientOpts}]), @@ -917,11 +1034,14 @@ ecacertfile(Config) when is_list(Config) -> Server1 = ssl_test_lib:start_server_error([{node, ServerNode}, - {port, Port}, {from, self()}, + {port, 0}, {from, self()}, {options, ServerBadOpts1}]), + + Port1 = ssl_test_lib:inet_port(Server1), + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, Port1}, {host, Hostname}, {from, self()}, {options, ClientOpts}]), @@ -942,198 +1062,58 @@ eoptions(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), - - %% Emulated opts - Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{active, trice} | ServerOpts]}]), - Client0 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, [{active, trice} | ClientOpts]}]), - ssl_test_lib:check_result(Server0, {error, {eoptions, {active,trice}}}, - Client0, {error, {eoptions, {active,trice}}}), - - test_server:sleep(500), - - Server1 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{header, a} | ServerOpts]}]), - Client1 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{header, a} | ClientOpts]}]), - ssl_test_lib:check_result(Server1, {error, {eoptions, {header, a}}}, - Client1, {error, {eoptions, {header, a}}}), - - test_server:sleep(500), - - - Server2 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{mode, a} | ServerOpts]}]), - - Client2 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, [{mode, a} | ClientOpts]}]), - ssl_test_lib:check_result(Server2, {error, {eoptions, {mode, a}}}, - Client2, {error, {eoptions, {mode, a}}}), - - - test_server:sleep(500), - - Server3 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{packet, 8.0} | ServerOpts]}]), - Client3 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, [{packet, 8.0} | ClientOpts]}]), - ssl_test_lib:check_result(Server3, {error, {eoptions, {packet, 8.0}}}, - Client3, {error, {eoptions, {packet, 8.0}}}), - - test_server:sleep(500), - - %% ssl - Server4 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{verify, 4} | ServerOpts]}]), - Client4 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{verify, 4} | ClientOpts]}]), - ssl_test_lib:check_result(Server4, {error, {eoptions, {verify, 4}}}, - Client4, {error, {eoptions, {verify, 4}}}), - - test_server:sleep(500), - - Server5 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{depth, four} | ServerOpts]}]), - Client5 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{depth, four} | ClientOpts]}]), - ssl_test_lib:check_result(Server5, {error, {eoptions, {depth, four}}}, - Client5, {error, {eoptions, {depth, four}}}), - - test_server:sleep(500), - - Server6 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{cacertfile, ""} | ServerOpts]}]), - Client6 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{cacertfile, ""} | ClientOpts]}]), - ssl_test_lib:check_result(Server6, {error, {eoptions, {cacertfile, ""}}}, - Client6, {error, {eoptions, {cacertfile, ""}}}), - - - test_server:sleep(500), - - Server7 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{certfile, 'cert.pem'} | ServerOpts]}]), - Client7 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{certfile, 'cert.pem'} | ClientOpts]}]), - ssl_test_lib:check_result(Server7, - {error, {eoptions, {certfile, 'cert.pem'}}}, - Client7, {error, {eoptions, {certfile, 'cert.pem'}}}), - - test_server:sleep(500), - - Server8 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{keyfile,'key.pem' } | ServerOpts]}]), - Client8 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, {options, [{keyfile, 'key.pem'} - | ClientOpts]}]), - ssl_test_lib:check_result(Server8, - {error, {eoptions, {keyfile, 'key.pem'}}}, - Client8, {error, {eoptions, {keyfile, 'key.pem'}}}), - - test_server:sleep(500), - - Server9 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{key, 'key.pem' } | ServerOpts]}]), - Client9 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, {options, [{key, 'key.pem'} - | ClientOpts]}]), - ssl_test_lib:check_result(Server9, {error, {eoptions, {key, 'key.pem'}}}, - Client9, {error, {eoptions, {key, 'key.pem'}}}), + Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> + ssl_test_lib:check_result(Server, + {error, {eoptions, {sslv2, Option}}}, + Client, + {error, {eoptions, {sslv2, Option}}}); + (Client, Server, Option) -> + ssl_test_lib:check_result(Server, + {error, {eoptions, Option}}, + Client, + {error, {eoptions, Option}}) + end, - test_server:sleep(500), - - Server10 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{password, foo} | ServerOpts]}]), - Client10 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{password, foo} | ClientOpts]}]), - ssl_test_lib:check_result(Server10, {error, {eoptions, {password, foo}}}, - Client10, {error, {eoptions, {password, foo}}}), - - test_server:sleep(500), - - %% Misc - Server11 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{ssl_imp, cool} | ServerOpts]}]), - Client11 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{ssl_imp, cool} | ClientOpts]}]), - ssl_test_lib:check_result(Server11, {error, {eoptions, {ssl_imp, cool}}}, - Client11, {error, {eoptions, {ssl_imp, cool}}}), - - - test_server:sleep(500), - - Server12 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, - {from, self()}, - {options, [{debug, cool} | ServerOpts]}]), - Client12 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{debug, cool} | ClientOpts]}]), - ssl_test_lib:check_result(Server12, {error, {eoptions, {debug, cool}}}, - Client12, {error, {eoptions, {debug, cool}}}). + TestOpts = [{versions, [sslv2, sslv3]}, + {ssl_imp, cool}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {validate_extensions_fun, function}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {debug, 1}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], + + [begin + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [TestOpt | ServerOpts]}]), + %% Will never reach a point where port is used. + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {host, Hostname}, {from, self()}, + {options, [TestOpt | ClientOpts]}]), + Check(Client, Server, TestOpt), + ok + end || TestOpt <- TestOpts], + ok. %%-------------------------------------------------------------------- shutdown(doc) -> @@ -1203,7 +1183,7 @@ shutdown_write(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, {error, closed}). shutdown_write_result(Socket, server) -> - test_server:sleep(500), + test_server:sleep(?SLEEP), ssl:shutdown(Socket, write); shutdown_write_result(Socket, client) -> ssl:recv(Socket, 0). @@ -1233,7 +1213,7 @@ shutdown_both(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, {error, closed}). shutdown_both_result(Socket, server) -> - test_server:sleep(500), + test_server:sleep(?SLEEP), ssl:shutdown(Socket, read_write); shutdown_both_result(Socket, client) -> ssl:recv(Socket, 0). @@ -1339,7 +1319,7 @@ reuse_session(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1350,7 +1330,7 @@ reuse_session(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1410,7 +1390,7 @@ reuse_session(Config) when is_list(Config) -> Server1 ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client4 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1457,7 +1437,7 @@ reuse_session_expired(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1468,7 +1448,7 @@ reuse_session_expired(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1530,7 +1510,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1541,7 +1521,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1725,8 +1705,122 @@ server_verify_none_active_once(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- + +server_verify_client_once_passive(doc) -> + ["Test server option verify_client_once"]; + +server_verify_client_once_passive(suite) -> + []; + +server_verify_client_once_passive(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(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, send_recv_result, []}}, + {options, [{active, false}, {verify, verify_peer}, + {verify_client_once, true} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client0, ok), + ssl_test_lib:close(Client0), + Server ! listen, + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, result_ok, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client1, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- + +server_verify_client_once_active(doc) -> + ["Test server option verify_client_once"]; + +server_verify_client_once_active(suite) -> + []; + +server_verify_client_once_active(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(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, send_recv_result_active, []}}, + {options, [{active, once}, {verify, verify_peer}, + {verify_client_once, true} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client0, ok), + ssl_test_lib:close(Client0), + Server ! listen, + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, result_ok, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Client1, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- + +server_verify_client_once_active_once(doc) -> + ["Test server option verify_client_once"]; + +server_verify_client_once_active_once(suite) -> + []; +server_verify_client_once_active_once(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(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, send_recv_result_active_once, []}}, + {options, [{active, once}, {verify, verify_peer}, + {verify_client_once, true} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active_once, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client0, ok), + ssl_test_lib:close(Client0), + Server ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, result_ok, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Client1, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + %%-------------------------------------------------------------------- server_verify_no_cacerts(doc) -> @@ -1744,7 +1838,68 @@ server_verify_no_cacerts(Config) when is_list(Config) -> | ServerOpts]}]), ssl_test_lib:check_result(Server, {error, {eoptions, {cacertfile, ""}}}). + +%%-------------------------------------------------------------------- + +server_require_peer_cert_ok(doc) -> + ["Test server option fail_if_no_peer_cert when peer sends cert"]; + +server_require_peer_cert_ok(suite) -> + []; + +server_require_peer_cert_ok(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + ClientOpts = ?config(client_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, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +server_require_peer_cert_fail(doc) -> + ["Test server option fail_if_no_peer_cert when peer doesn't send cert"]; + +server_require_peer_cert_fail(suite) -> + []; + +server_require_peer_cert_fail(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ?config(server_verification_opts, Config)], + BadClientOpts = ?config(client_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, no_result, []}}, + {options, [{active, false} | ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, no_result, []}}, + {options, [{active, false} | BadClientOpts]}]), + + ssl_test_lib:check_result(Server, {error, esslaccept}, + Client, {error, esslconnect}), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- client_verify_none_passive(doc) -> @@ -1849,31 +2004,289 @@ client_verify_none_active_once(Config) when is_list(Config) -> ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_renegotiate(doc) -> + ["Test ssl:renegotiate/1 on client."]; + +client_renegotiate(suite) -> + []; + +client_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {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, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +server_renegotiate(doc) -> + ["Test ssl:renegotiate/1 on server."]; + +server_renegotiate(suite) -> + []; + +server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {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, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok. + +%%-------------------------------------------------------------------- +client_no_wrap_sequence_number(doc) -> + ["Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."]; + +client_no_wrap_sequence_number(suite) -> + []; + +client_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to erlang", + N = 10, + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_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, {ssl_test_lib, + trigger_renegotiate, [[ErlData, N+2]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +server_no_wrap_sequence_number(doc) -> + ["Test that erlang server will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."]; + +server_no_wrap_sequence_number(suite) -> + []; + +server_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + N = 10, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N} | 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, no_result, []}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok. + +%%-------------------------------------------------------------------- +extended_key_usage(doc) -> + ["Test cert that has a critical extended_key_usage extension"]; + +extended_key_usage(suite) -> + []; + +extended_key_usage(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + PrivDir = ?config(priv_dir, Config), + + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + NewCertFile = filename:join(PrivDir, "cert.pem"), + + {ok, [{cert, DerCert, _}]} = public_key:pem_to_der(CertFile), + + {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile), + + {ok, Key} = public_key:decode_private_key(KeyInfo), + + {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp), + + ExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-serverAuth']}, + + OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, + + Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions, + + NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{extensions = [ExtKeyUsageExt |Extensions]}, + + NewDerCert = public_key:sign(NewOTPTbsCert, Key), + + public_key:der_to_pem(NewCertFile, [{cert, NewDerCert}]), + + NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, NewServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +validate_extensions_fun(doc) -> + ["Test that it is possible to specify a validate_extensions_fun"]; + +validate_extensions_fun(suite) -> + []; + +validate_extensions_fun(Config) when is_list(Config) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + + Fun = fun(Extensions, State, _, AccError) -> + {Extensions, State, AccError} + end, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, [{validate_extensions_fun, Fun}, + {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, {?MODULE, send_recv_result_active, []}}, + {options,[{validate_extensions_fun, Fun} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- send_recv_result(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), - {ok,"Hejhopp"} = ssl:recv(Socket, 7), + ssl:send(Socket, "Hello world"), + {ok,"Hello world"} = ssl:recv(Socket, 11), ok. send_recv_result_active(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), + ssl:send(Socket, "Hello world"), receive - {ssl, Socket, "Hejhopp"} -> + {ssl, Socket, "Hello world"} -> ok end. send_recv_result_active_once(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), + ssl:send(Socket, "Hello world"), receive - {ssl, Socket, "Hejhopp"} -> + {ssl, Socket, "Hello world"} -> ok end. +result_ok(_Socket) -> + ok. + +renegotiate(Socket, Data) -> + test_server:format("Renegotiating ~n", []), + Result = ssl:renegotiate(Socket), + test_server:format("Result ~p~n", [Result]), + ssl:send(Socket, Data), + case Result of + ok -> + ok; + %% It is not an error in erlang ssl + %% if peer rejects renegotiation. + %% Connection will stay up + {error, renegotiation_rejected} -> + ok; + Other -> + Other + end. + session_cache_process_list(doc) -> ["Test reuse of sessions (short handshake)"]; @@ -1909,7 +2322,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1920,7 +2333,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1963,7 +2376,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Server1 ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client4 = ssl_test_lib:start_client([{node, ClientNode}, @@ -2112,3 +2525,14 @@ session_loop(Sess) -> session_loop(Sess) end. +erlang_ssl_receive(Socket, Data) -> + receive + {ssl, Socket, Data} -> + io:format("Received ~p~n",[Data]), + ok; + Other -> + test_server:fail({unexpected_message, Other}) + after ?SLEEP * 3 -> + test_server:fail({did_not_get, Data}) + end. + diff --git a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem b/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem new file mode 100644 index 0000000000..feb581da30 --- /dev/null +++ b/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem @@ -0,0 +1,5 @@ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAMY5VmCZ22ZEy/KO8kjt94PH7ZtSG0Z0zitlMlvd4VsNkDzXsVeu+wkH +FGDC3h3vgv6iwXGCbmrSOVk/FPZbzLhwZ8aLnkUFOBbOvVvb1JptQwOt8mf+eScG +M2gGBktheQV5Nf1IrzOctG7VGt+neiqb/Y86uYCcDdL+M8++0qnLAgEC +-----END DH PARAMETERS----- diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index f031552457..1bcb9a657b 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -144,7 +144,9 @@ all(suite) -> packet_wait_passive, packet_wait_active, packet_baddata_passive, packet_baddata_active, packet_size_passive, packet_size_active, - packet_erl_decode + packet_erl_decode, + packet_http_decode, + packet_http_bin_decode_multi ]. %% Test cases starts here. @@ -1466,6 +1468,173 @@ client_packet_decode(Socket, CDR) -> end, ok. +%%-------------------------------------------------------------------- +packet_http_decode(doc) -> + ["Test setting the packet option {packet, http}"]; +packet_http_decode(suite) -> + []; + +packet_http_decode(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Request = "GET / HTTP/1.1\r\n" + "host: www.example.com\r\n" + "user-agent: HttpTester\r\n" + "\r\n", + Response = "HTTP/1.1 200 OK\r\n" + "\r\n" + "Hello!", + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, server_http_decode, [Response]}}, + {options, [{active, true}, binary, {packet, http} | + ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, client_http_decode, [Request]}}, + {options, [{active, true}, binary, {packet, http} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +server_http_decode(Socket, HttpResponse) -> + assert_packet_opt(Socket, http), + receive + {ssl, Socket, {http_request, 'GET', _, {1,1}}} -> ok; + Other1 -> exit({?LINE, Other1}) + end, + assert_packet_opt(Socket, http), + receive + {ssl, Socket, {http_header, _, 'Host', _, "www.example.com"}} -> ok; + Other2 -> exit({?LINE, Other2}) + end, + assert_packet_opt(Socket, http), + receive + {ssl, Socket, {http_header, _, 'User-Agent', _, "HttpTester"}} -> ok; + Other3 -> exit({?LINE, Other3}) + end, + assert_packet_opt(Socket, http), + receive + {ssl, Socket, http_eoh} -> ok; + Other4 -> exit({?LINE, Other4}) + end, + assert_packet_opt(Socket, http), + ok = ssl:send(Socket, HttpResponse), + ok. + +client_http_decode(Socket, HttpRequest) -> + ok = ssl:send(Socket, HttpRequest), + receive + {ssl, Socket, {http_response, {1,1}, 200, "OK"}} -> ok; + Other1 -> exit({?LINE, Other1}) + end, + receive + {ssl, Socket, http_eoh} -> ok; + Other2 -> exit({?LINE, Other2}) + end, + ok = ssl:setopts(Socket, [{packet, 0}]), + receive + {ssl, Socket, <<"Hello!">>} -> ok; + Other3 -> exit({?LINE, Other3}) + end, + ok. + +%%-------------------------------------------------------------------- +packet_http_bin_decode_multi(doc) -> + ["Test setting the packet option {packet, http_bin} with multiple requests"]; +packet_http_bin_decode_multi(suite) -> + []; + +packet_http_bin_decode_multi(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Request = <<"GET / HTTP/1.1\r\n" + "host: www.example.com\r\n" + "user-agent: HttpTester\r\n" + "\r\n">>, + Response = <<"HTTP/1.1 200 OK\r\n" + "\r\n" + "Hello!">>, + NumMsgs = 3, + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, server_http_bin_decode, [Response, NumMsgs]}}, + {options, [{active, true}, binary, {packet, http_bin} | + ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, client_http_bin_decode, [Request, NumMsgs]}}, + {options, [{active, true}, binary, {packet, http_bin} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +server_http_bin_decode(Socket, HttpResponse, Count) when Count > 0 -> + assert_packet_opt(Socket, http_bin), + receive + {ssl, Socket, {http_request, 'GET', _, {1,1}}} -> ok; + Other1 -> exit({?LINE, Other1}) + end, + assert_packet_opt(Socket, http_bin), + receive + {ssl, Socket, {http_header, _, 'Host', _, <<"www.example.com">>}} -> ok; + Other2 -> exit({?LINE, Other2}) + end, + assert_packet_opt(Socket, http_bin), + receive + {ssl, Socket, {http_header, _, 'User-Agent', _, <<"HttpTester">>}} -> ok; + Other3 -> exit({?LINE, Other3}) + end, + assert_packet_opt(Socket, http_bin), + receive + {ssl, Socket, http_eoh} -> ok; + Other4 -> exit({?LINE, Other4}) + end, + assert_packet_opt(Socket, http_bin), + ok = ssl:send(Socket, HttpResponse), + server_http_bin_decode(Socket, HttpResponse, Count - 1); +server_http_bin_decode(_, _, _) -> + ok. + +client_http_bin_decode(Socket, HttpRequest, Count) when Count > 0 -> + ok = ssl:send(Socket, HttpRequest), + receive + {ssl, Socket, {http_response, {1,1}, 200, <<"OK">>}} -> ok; + Other1 -> exit({?LINE, Other1}) + end, + receive + {ssl, Socket, http_eoh} -> ok; + Other2 -> exit({?LINE, Other2}) + end, + ok = ssl:setopts(Socket, [{packet, 0}]), + receive + {ssl, Socket, <<"Hello!">>} -> ok; + Other3 -> exit({?LINE, Other3}) + end, + ok = ssl:setopts(Socket, [{packet, http_bin}]), + client_http_bin_decode(Socket, HttpRequest, Count - 1); +client_http_bin_decode(_, _, _) -> + ok. %%-------------------------------------------------------------------- %% Internal functions @@ -1572,3 +1741,6 @@ active_packet(Socket, Data, N) -> Other -> {other, Other, ssl:session_info(Socket),N} end. + +assert_packet_opt(Socket, Type) -> + {ok, [{packet, Type}]} = ssl:getopts(Socket, [packet]). diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 2df2e70679..00c5350ad0 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -26,6 +26,7 @@ %% Note: This directive should only be used in test suites. -compile(export_all). +-record(sslsocket, { fd = nil, pid = nil}). timetrap(Time) -> Mul = try @@ -52,7 +53,11 @@ node_to_hostip(Node) -> Address. start_server(Args) -> - spawn_link(?MODULE, run_server, [Args]). + Result = spawn_link(?MODULE, run_server, [Args]), + receive + {listen, up} -> + Result + end. run_server(Opts) -> Node = proplists:get_value(node, Opts), @@ -61,23 +66,14 @@ run_server(Opts) -> Pid = proplists:get_value(from, Opts), test_server:format("ssl:listen(~p, ~p)~n", [Port, Options]), {ok, ListenSocket} = rpc:call(Node, ssl, listen, [Port, Options]), - case Port of - 0 -> - {ok, {_, NewPort}} = ssl:sockname(ListenSocket), - Pid ! {self(), {port, NewPort}}; - _ -> - ok - end, + Pid ! {listen, up}, + send_selected_port(Pid, Port, ListenSocket), run_server(ListenSocket, Opts). run_server(ListenSocket, Opts) -> + AcceptSocket = connect(ListenSocket, Opts), Node = proplists:get_value(node, Opts), Pid = proplists:get_value(from, Opts), - test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), - test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), - ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("Server: apply(~p,~p,~p)~n", [Module, Function, [AcceptSocket | Args]]), @@ -85,6 +81,7 @@ run_server(ListenSocket, Opts) -> no_result_msg -> ok; Msg -> + test_server:format("Msg: ~p ~n", [Msg]), Pid ! {self(), Msg} end, receive @@ -94,8 +91,44 @@ run_server(ListenSocket, Opts) -> ok = rpc:call(Node, ssl, close, [AcceptSocket]) end. +%%% To enable to test with s_client -reconnect +connect(ListenSocket, Opts) -> + Node = proplists:get_value(node, Opts), + ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0), + AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy), + case ReconnectTimes of + 0 -> + AcceptSocket; + _ -> + remove_close_msg(ReconnectTimes), + AcceptSocket + end. + +connect(_, _, 0, AcceptSocket) -> + AcceptSocket; +connect(ListenSocket, Node, N, _) -> + test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), + {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, + [ListenSocket]), + test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), + ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), + connect(ListenSocket, Node, N-1, AcceptSocket). + +remove_close_msg(0) -> + ok; +remove_close_msg(ReconnectTimes) -> + receive + {ssl_closed, _} -> + remove_close_msg(ReconnectTimes -1) + end. + + start_client(Args) -> - spawn_link(?MODULE, run_client, [Args]). + Result = spawn_link(?MODULE, run_client, [Args]), + receive + connected -> + Result + end. run_client(Opts) -> Node = proplists:get_value(node, Opts), @@ -106,14 +139,11 @@ run_client(Opts) -> test_server:format("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), case rpc:call(Node, ssl, connect, [Host, Port, Options]) of {ok, Socket} -> + Pid ! connected, test_server:format("Client: connected~n", []), - case proplists:get_value(port, Options) of - 0 -> - {ok, {_, NewPort}} = ssl:sockname(Socket), - Pid ! {self(), {port, NewPort}}; - _ -> - ok - end, + %% In specail cases we want to know the client port, it will + %% be indicated by sending {port, 0} in options list! + send_selected_port(Pid, proplists:get_value(port, Options), Socket), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("Client: apply(~p,~p,~p)~n", [Module, Function, [Socket | Args]]), @@ -178,6 +208,26 @@ check_result(Pid, Msg) -> test_server:fail(Reason) end. +check_result_ignore_renegotiation_reject(Pid, Msg) -> + receive + {Pid, fail_session_fatal_alert_during_renegotiation} -> + test_server:comment("Server rejected old renegotiation"), + ok; + {ssl_error, _, esslconnect} -> + test_server:comment("Server rejected old renegotiation"), + ok; + {Pid, Msg} -> + ok; + {Port, {data,Debug}} when is_port(Port) -> + io:format("openssl ~s~n",[Debug]), + check_result(Pid,Msg); + Unexpected -> + Reason = {{expected, {Pid, Msg}}, + {got, Unexpected}}, + test_server:fail(Reason) + end. + + wait_for_result(Server, ServerMsg, Client, ClientMsg) -> receive {Server, ServerMsg} -> @@ -237,7 +287,7 @@ cert_options(Config) -> "badcert.pem"]), BadKeyFile = filename:join([?config(priv_dir, Config), "badkey.pem"]), - [{client_opts, [{ssl_imp, new}]}, + [{client_opts, [{ssl_imp, new},{reuseaddr, true}]}, {client_verification_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, @@ -269,7 +319,11 @@ cert_options(Config) -> start_upgrade_server(Args) -> - spawn_link(?MODULE, run_upgrade_server, [Args]). + Result = spawn_link(?MODULE, run_upgrade_server, [Args]), + receive + {listen, up} -> + Result + end. run_upgrade_server(Opts) -> Node = proplists:get_value(node, Opts), @@ -281,15 +335,8 @@ run_upgrade_server(Opts) -> test_server:format("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]), {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), - - case Port of - 0 -> - {ok, {_, NewPort}} = inet:sockname(ListenSocket), - Pid ! {self(), {port, NewPort}}; - _ -> - ok - end, - + Pid ! {listen, up}, + send_selected_port(Pid, Port, ListenSocket), test_server:format("gen_tcp:accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), @@ -331,14 +378,8 @@ run_upgrade_client(Opts) -> test_server:format("gen_tcp:connect(~p, ~p, ~p)~n", [Host, Port, TcpOptions]), {ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]), - - case proplists:get_value(port, Opts) of - 0 -> - {ok, {_, NewPort}} = inet:sockname(Socket), - Pid ! {self(), {port, NewPort}}; - _ -> - ok - end, + + send_selected_port(Pid, Port, Socket), test_server:format("ssl:connect(~p, ~p)~n", [Socket, SslOptions]), {ok, SslSocket} = rpc:call(Node, ssl, connect, [Socket, SslOptions]), @@ -354,7 +395,11 @@ run_upgrade_client(Opts) -> end. start_server_error(Args) -> - spawn_link(?MODULE, run_server_error, [Args]). + Result = spawn_link(?MODULE, run_server_error, [Args]), + receive + {listen, up} -> + Result + end. run_server_error(Opts) -> Node = proplists:get_value(node, Opts), @@ -364,8 +409,10 @@ run_server_error(Opts) -> test_server:format("ssl:listen(~p, ~p)~n", [Port, Options]), case rpc:call(Node, ssl, listen, [Port, Options]) of {ok, ListenSocket} -> - test_server:sleep(2000), %% To make sure error_client will + %% To make sure error_client will %% get {error, closed} and not {error, connection_refused} + Pid ! {listen, up}, + send_selected_port(Pid, Port, ListenSocket), test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), case rpc:call(Node, ssl, transport_accept, [ListenSocket]) of {error, _} = Error -> @@ -376,6 +423,9 @@ run_server_error(Opts) -> Pid ! {self(), Error} end; Error -> + %% Not really true but as this is an error test + %% this is what we want. + Pid ! {listen, up}, Pid ! {self(), Error} end. @@ -400,8 +450,8 @@ inet_port(Pid) when is_pid(Pid)-> inet_port(Node) -> {Port, Socket} = do_inet_port(Node), - rpc:call(Node, gen_tcp, close, [Socket]), - Port. + rpc:call(Node, gen_tcp, close, [Socket]), + Port. do_inet_port(Node) -> {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]), @@ -410,3 +460,37 @@ do_inet_port(Node) -> no_result(_) -> no_result_msg. + +trigger_renegotiate(Socket, [ErlData, N]) -> + [{session_id, Id} | _ ] = ssl:session_info(Socket), + trigger_renegotiate(Socket, ErlData, N, Id). + +trigger_renegotiate(Socket, _, 0, Id) -> + test_server:sleep(1000), + case ssl:session_info(Socket) of + [{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; + {error, closed} -> + fail_session_fatal_alert_during_renegotiation; + {error, timeout} -> + fail_timeout + end; + +trigger_renegotiate(Socket, ErlData, N, Id) -> + ssl:send(Socket, ErlData), + trigger_renegotiate(Socket, ErlData, N-1, Id). + + +send_selected_port(Pid, 0, #sslsocket{} = Socket) -> + {ok, {_, NewPort}} = ssl:sockname(Socket), + Pid ! {self(), {port, NewPort}}; +send_selected_port(Pid, 0, Socket) -> + {ok, {_, NewPort}} = inet:sockname(Socket), + Pid ! {self(), {port, NewPort}}; +send_selected_port(_,_,_) -> + ok. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index c079e12b83..cbf0447bf0 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -30,6 +30,9 @@ -define(TIMEOUT, 120000). -define(SLEEP, 1000). +-define(OPENSSL_RENEGOTIATE, "r\n"). +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_GARBAGE, "P\n"). %% Test server callback functions %%-------------------------------------------------------------------- @@ -114,6 +117,11 @@ all(doc) -> all(suite) -> [erlang_client_openssl_server, erlang_server_openssl_client, + erlang_server_openssl_client_reuse_session, + erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_no_wrap_sequence_number, + erlang_server_openssl_client_no_wrap_sequence_number, + erlang_client_openssl_server_no_server_ca_cert, ssl3_erlang_client_openssl_server, ssl3_erlang_server_openssl_client, ssl3_erlang_client_openssl_server_client_cert, @@ -124,7 +132,8 @@ all(suite) -> tls1_erlang_client_openssl_server_client_cert, tls1_erlang_server_openssl_client_client_cert, tls1_erlang_server_erlang_client_client_cert, - ciphers + ciphers, + erlang_client_bad_openssl_server ]. %% Test cases starts here. @@ -148,13 +157,13 @@ erlang_client_openssl_server(Config) when is_list(Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, + " -cert " ++ CertFile ++ " -key " ++ KeyFile, test_server:format("openssl cmd: ~p~n", [Cmd]), OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -193,14 +202,199 @@ erlang_server_openssl_client(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ + " -host localhost", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_openssl_client_reuse_session(doc) -> + ["Test erlang server with openssl client that reconnects with the" + "same session id, to test reusing of sessions."]; +erlang_server_openssl_client_reuse_session(suite) -> + []; +erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {reconnect_times, 5}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ + " -host localhost -reconnect", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_renegotiate(doc) -> + ["Test erlang client when openssl server issuses a renegotiate"]; +erlang_client_openssl_server_renegotiate(suite) -> + []; +erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + delayed_send, [[ErlData, OpenSslData]]}}, + {options, ClientOpts}]), + + port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), test_server:sleep(?SLEEP), + port_command(OpensslPort, OpenSslData), + + %%ssl_test_lib:check_result(Client, ok), + %% Currently allow test case to not fail + %% if server requires secure renegotiation from RFC-5746 + %% This should be removed as soon as we have implemented it. + ssl_test_lib:check_result_ignore_renegotiation_reject(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_no_wrap_sequence_number(doc) -> + ["Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."]; +erlang_client_openssl_server_no_wrap_sequence_number(suite) -> + []; +erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl\n", + N = 10, + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, N+2]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + %%ssl_test_lib:check_result(Client, ok), + %% Currently allow test case to not fail + %% if server requires secure renegotiation from RFC-5746 + %% This should be removed as soon as we have implemented it. + ssl_test_lib:check_result_ignore_renegotiation_reject(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +erlang_server_openssl_client_no_wrap_sequence_number(doc) -> + ["Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."]; + +erlang_server_openssl_client_no_wrap_sequence_number(suite) -> + []; +erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + N = 10, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ - " -host localhost", + " -host localhost -msg", test_server:format("openssl cmd: ~p~n", [Cmd]), OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), @@ -210,6 +404,53 @@ erlang_server_openssl_client(Config) when is_list(Config) -> close_port(OpenSslPort), process_flag(trap_exit, false), ok. +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_no_server_ca_cert(doc) -> + ["Test erlang client when openssl server sends a cert chain not" + "including the ca cert. Explicitly test this even if it is" + "implicitly tested eleswhere."]; +erlang_client_openssl_server_no_server_ca_cert(suite) -> + []; +erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), + + port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. %%-------------------------------------------------------------------- ssl3_erlang_client_openssl_server(doc) -> @@ -233,7 +474,7 @@ ssl3_erlang_client_openssl_server(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -268,8 +509,6 @@ ssl3_erlang_server_openssl_client(Config) when is_list(Config) -> {options, [{versions, [sslv3]} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - - test_server:sleep(?SLEEP), Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ " -host localhost -ssl3", @@ -300,8 +539,8 @@ ssl3_erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ @@ -312,7 +551,7 @@ ssl3_erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -354,8 +593,6 @@ ssl3_erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - test_server:sleep(?SLEEP), - CaCertFile = proplists:get_value(cacertfile, ClientOpts), CertFile = proplists:get_value(certfile, ClientOpts), KeyFile = proplists:get_value(keyfile, ClientOpts), @@ -402,8 +639,6 @@ ssl3_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - test_server:sleep(?SLEEP), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -439,14 +674,13 @@ tls1_erlang_client_openssl_server(Config) when is_list(Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile - ++ " -key " ++ KeyFile ++ " -tls1", + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -tls1", test_server:format("openssl cmd: ~p~n", [Cmd]), OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -484,8 +718,6 @@ tls1_erlang_server_openssl_client(Config) when is_list(Config) -> [{versions, [tlsv1]} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - test_server:sleep(?SLEEP), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ " -host localhost -tls1", @@ -529,7 +761,7 @@ tls1_erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -571,8 +803,6 @@ tls1_erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - test_server:sleep(?SLEEP), - CaCertFile = proplists:get_value(cacertfile, ClientOpts), CertFile = proplists:get_value(certfile, ClientOpts), KeyFile = proplists:get_value(keyfile, ClientOpts), @@ -617,8 +847,6 @@ tls1_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - test_server:sleep(?SLEEP), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -668,14 +896,13 @@ cipher(CipherSuite, Version, Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile - ++ " -key " ++ KeyFile ++ "", + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", test_server:format("openssl cmd: ~p~n", [Cmd]), OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - test_server:sleep(?SLEEP), + wait_for_openssl_server(), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -707,6 +934,52 @@ cipher(CipherSuite, Version, Config) -> Return. %%-------------------------------------------------------------------- +erlang_client_bad_openssl_server(doc) -> + [""]; +erlang_client_bad_openssl_server(suite) -> + []; +erlang_client_bad_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ?config(client_verification_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [tlsv1]} | ClientOpts]}]), + + %% Send garbage + port_command(OpensslPort, ?OPENSSL_GARBAGE), + + test_server:sleep(?SLEEP), + + Client ! server_sent_garbage, + + ssl_test_lib:check_result(Client, true), + + ssl_test_lib:close(Client), + %% Clean close down! + close_port(OpensslPort), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- erlang_ssl_receive(Socket, Data) -> test_server:format("Connection info: ~p~n", @@ -738,8 +1011,14 @@ connection_info(Socket, Version) -> connection_info_result(Socket) -> ssl:connection_info(Socket). + +delayed_send(Socket, [ErlData, OpenSslData]) -> + test_server:sleep(?SLEEP), + ssl:send(Socket, ErlData), + erlang_ssl_receive(Socket, OpenSslData). + close_port(Port) -> - port_command(Port, "Q\n"), + port_command(Port, ?OPENSSL_QUIT), %%catch port_command(Port, "quit\n"), close_loop(Port, 500, false). @@ -770,3 +1049,22 @@ close_loop(Port, Time, SentClose) -> io:format("Timeout~n",[]) end end. + + +server_sent_garbage(Socket) -> + receive + server_sent_garbage -> + {error, closed} == ssl:send(Socket, "data") + end. + +wait_for_openssl_server() -> + receive + {Port, {data, Debug}} when is_port(Port) -> + io:format("openssl ~s~n",[Debug]), + %% openssl has started make sure + %% it will be in accept. Parsing + %% output is too error prone. (Even + %% more so than sleep!) + test_server:sleep(?SLEEP) + end. + diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index bf67fcd257..337ea4b380 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -17,8 +17,16 @@ # %CopyrightEnd% # -SSL_VSN = 3.10.9 -TICKETS = OTP-8510 +SSL_VSN = 3.11 + +TICKETS = OTP-8517 \ + OTP-7046 \ + OTP-8557 \ + OTP-8560 \ + OTP-8545 \ + OTP-8554 + +#TICKETS_3.10.9 = OTP-8510 #TICKETS_3.10.8 = OTP-8372 OTP-8441 OTP-8459 #TICKETS_3.10.7 = OTP-8260 OTP-8218 OTP-8250 |