diff options
Diffstat (limited to 'lib/ssl/test')
24 files changed, 2997 insertions, 924 deletions
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 0d241707d9..999df320a3 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -3,16 +3,17 @@ # # Copyright Ericsson AB 1999-2015. 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/. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# 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. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # @@ -36,7 +37,9 @@ VSN=$(GS_VSN) MODULES = \ ssl_test_lib \ + ssl_alpn_handshake_SUITE \ ssl_basic_SUITE \ + ssl_bench_SUITE \ ssl_cipher_SUITE \ ssl_certificate_verify_SUITE\ ssl_crl_SUITE\ @@ -50,6 +53,8 @@ MODULES = \ ssl_session_cache_SUITE \ ssl_to_openssl_SUITE \ ssl_ECC_SUITE \ + ssl_upgrade_SUITE\ + ssl_sni_SUITE \ make_certs\ erl_make_certs @@ -131,7 +136,7 @@ release_spec: opt release_tests_spec: opt $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(HRL_FILES_NEEDED_IN_TEST) $(COVER_FILE) "$(RELSYSDIR)" - $(INSTALL_DATA) ssl.spec ssl.cover "$(RELSYSDIR)" + $(INSTALL_DATA) ssl.spec ssl_bench.spec ssl.cover "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl index daf4466f11..8e909a5b74 100644 --- a/lib/ssl/test/erl_make_certs.erl +++ b/lib/ssl/test/erl_make_certs.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2011-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -114,7 +115,7 @@ verify_signature(DerEncodedCert, DerKey, _KeyParams) -> #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, - parameters = Params, publicKey = {0, PubKey}} -> + parameters = Params, publicKey = PubKey} -> public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) end. @@ -204,7 +205,7 @@ issuer_der(Issuer) -> Subject. subject(undefined, IsRootCA) -> - User = if IsRootCA -> "RootCA"; true -> user() end, + User = if IsRootCA -> "RootCA"; true -> os:getenv("USER", "test_user") end, Opts = [{email, User ++ "@erlang.org"}, {name, User}, {city, "Stockholm"}, @@ -215,14 +216,6 @@ subject(undefined, IsRootCA) -> subject(Opts, _) -> subject(Opts). -user() -> - case os:getenv("USER") of - false -> - "test_user"; - User -> - User - end. - subject(SubjectOpts) when is_list(SubjectOpts) -> Encode = fun(Opt) -> {Type,Value} = subject_enc(Opt), @@ -300,7 +293,7 @@ publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> publickey(#'ECPrivateKey'{version = _Version, privateKey = _PrivKey, parameters = Params, - publicKey = {0, PubKey}}) -> + publicKey = PubKey}) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = #'ECPoint'{point = PubKey}}. @@ -409,9 +402,9 @@ gen_ec2(CurveId) -> {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), #'ECPrivateKey'{version = 1, - privateKey = binary_to_list(PrivKey), + privateKey = PrivKey, parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, - publicKey = {0, PubKey}}. + publicKey = PubKey}. %% See fips_186-3.pdf dsa_search(T, P0, Q, Iter) when Iter > 0 -> diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 15a7e118ff..7215a59823 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. 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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -81,7 +82,7 @@ all(DataDir, PrivDir, C = #config{}) -> create_rnd(DataDir, PrivDir), % For all requests rootCA(PrivDir, "erlangCA", C), intermediateCA(PrivDir, "otpCA", "erlangCA", C), - endusers(PrivDir, "otpCA", ["client", "server", "revoked"], C), + endusers(PrivDir, "otpCA", ["client", "server", "revoked", "a.server", "b.server"], C), endusers(PrivDir, "erlangCA", ["localhost"], C), %% Create keycert files SDir = filename:join([PrivDir, "server"]), @@ -324,8 +325,9 @@ eval_cmd(Port, Cmd) -> ok end, receive - {Port, {exit_status, Status}} when Status /= 0 -> - %% io:fwrite("exit status: ~w~n", [Status]), + {Port, {exit_status, 0}} -> + ok; + {Port, {exit_status, Status}} -> exit({eval_cmd, Cmd, Status}) after 0 -> ok @@ -369,7 +371,7 @@ req_cnf(C) -> "subjectKeyIdentifier = hash\n" "subjectAltName = email:copy\n"]. -ca_cnf(C) -> +ca_cnf(C = #config{issuing_distribution_point = true}) -> ["# Purpose: Configuration for CAs.\n" "\n" "ROOTDIR = $ENV::ROOTDIR\n" @@ -446,5 +448,83 @@ ca_cnf(C) -> "subjectAltName = email:copy\n" "issuerAltName = issuer:copy\n" "crlDistributionPoints=@crl_section\n" - ]. + ]; + +ca_cnf(C = #config{issuing_distribution_point = false}) -> + ["# Purpose: Configuration for CAs.\n" + "\n" + "ROOTDIR = $ENV::ROOTDIR\n" + "default_ca = ca\n" + "\n" + + "[ca]\n" + "dir = $ROOTDIR/", C#config.commonName, "\n" + "certs = $dir/certs\n" + "crl_dir = $dir/crl\n" + "database = $dir/index.txt\n" + "new_certs_dir = $dir/newcerts\n" + "certificate = $dir/cert.pem\n" + "serial = $dir/serial\n" + "crl = $dir/crl.pem\n", + ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls], + "private_key = $dir/private/key.pem\n" + "RANDFILE = $dir/private/RAND\n" + "\n" + "x509_extensions = user_cert\n", + ["crl_extensions = crl_ext\n" || C#config.v2_crls], + "unique_subject = no\n" + "default_days = 3600\n" + "default_md = md5\n" + "preserve = no\n" + "policy = policy_match\n" + "\n" + + "[policy_match]\n" + "commonName = supplied\n" + "organizationalUnitName = optional\n" + "organizationName = match\n" + "countryName = match\n" + "localityName = match\n" + "emailAddress = supplied\n" + "\n" + + "[crl_ext]\n" + "authorityKeyIdentifier=keyid:always,issuer:always\n", + %["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point], + + %"[idpsec]\n" + %"fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n" + + "[user_cert]\n" + "basicConstraints = CA:false\n" + "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" + %"crlDistributionPoints=@crl_section\n" + + %%"[crl_section]\n" + %% intentionally invalid + %%"URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" + %%"URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n" + %%"\n" + + "[user_cert_digital_signature_only]\n" + "basicConstraints = CA:false\n" + "keyUsage = digitalSignature\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" + "\n" + "[ca_cert]\n" + "basicConstraints = critical,CA:true\n" + "keyUsage = cRLSign, keyCertSign\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid:always,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" + %"crlDistributionPoints=@crl_section\n" + ]. diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec index fc7c1bbb82..86e14c033e 100644 --- a/lib/ssl/test/ssl.spec +++ b/lib/ssl/test/ssl.spec @@ -1 +1,4 @@ {suites,"../ssl_test",all}. +{skip_cases, "../ssl_test", + ssl_bench_SUITE, [setup_sequential, setup_concurrent, payload_simple], + "Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index 3566a8a0a5..3a9f21ea99 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2007-2014. 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/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -31,8 +32,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, @@ -47,7 +46,7 @@ groups() -> {'tlsv1', [], all_versions_groups()}, {'erlang_server', [], key_cert_combinations()}, {'erlang_client', [], key_cert_combinations()}, - {'erlang', [], key_cert_combinations()} + {'erlang', [], key_cert_combinations() ++ misc()} ]. all_versions_groups ()-> @@ -66,16 +65,17 @@ key_cert_combinations() -> client_rsa_server_ecdsa ]. +misc()-> + [client_ecdsa_server_ecdsa_with_raw_key]. + %%-------------------------------------------------------------------- init_per_suite(Config0) -> end_per_suite(Config0), try crypto:start() of ok -> %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), Config1 = ssl_test_lib:make_ecdsa_cert(Config0), Config2 = ssl_test_lib:make_ecdh_rsa_cert(Config1), ssl_test_lib:cert_options(Config2) @@ -146,6 +146,7 @@ init_per_testcase(TestCase, Config) -> ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), end_per_testcase(TestCase, Config), ssl:start(), + ct:timetrap({seconds, 15}), Config. end_per_testcase(_TestCase, Config) -> @@ -191,6 +192,32 @@ client_rsa_server_ecdsa(Config) when is_list(Config) -> SOpts = ?config(server_ecdsa_verify_opts, Config), basic_test(COpts, SOpts, Config). +client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> + COpts = ?config(client_ecdsa_opts, Config), + SOpts = ?config(server_ecdsa_verify_opts, Config), + ServerCert = proplists:get_value(certfile, SOpts), + ServerKeyFile = proplists:get_value(keyfile, SOpts), + {ok, PemBin} = file:read_file(ServerKeyFile), + PemEntries = public_key:pem_decode(PemBin), + {'ECPrivateKey', Key, not_encrypted} = proplists:lookup('ECPrivateKey', PemEntries), + ServerKey = {'ECPrivateKey', Key}, + ServerCA = proplists:get_value(cacertfile, SOpts), + ClientCert = proplists:get_value(certfile, COpts), + ClientKey = proplists:get_value(keyfile, COpts), + ClientCA = proplists:get_value(cacertfile, COpts), + SType = ?config(server_type, Config), + CType = ?config(client_type, Config), + {Server, Port} = start_server_with_raw_key(SType, + ClientCA, ServerCA, + ServerCert, + ServerKey, + Config), + Client = start_client(CType, Port, ServerCA, ClientCA, + ClientCert, + ClientKey, Config), + check_result(Server, SType, Client, CType), + close(Server, Client). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -247,12 +274,9 @@ start_server(openssl, CA, OwnCa, Cert, Key, Config) -> " -verify 2 -cert " ++ Cert ++ " -CAfile " ++ NewCA ++ " -key " ++ Key ++ " -msg -debug", OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), true = port_command(OpenSslPort, "Hello world"), {OpenSslPort, Port}; - start_server(erlang, CA, _, Cert, Key, Config) -> - {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -263,6 +287,17 @@ start_server(erlang, CA, _, Cert, Key, Config) -> [{verify, verify_peer}, {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]), {Server, ssl_test_lib:inet_port(Server)}. +start_server_with_raw_key(erlang, CA, _, Cert, Key, Config) -> + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, + []}}, + {options, + [{verify, verify_peer}, {cacertfile, CA}, + {certfile, Cert}, {key, Key}]}]), + {Server, ssl_test_lib:inet_port(Server)}. check_result(Server, erlang, Client, erlang) -> ssl_test_lib:check_result(Server, ok, Client, ok); diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl new file mode 100644 index 0000000000..f5469ec8e0 --- /dev/null +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -0,0 +1,419 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_alpn_handshake_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 500). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [ + {'tlsv1.2', [], alpn_tests()}, + {'tlsv1.1', [], alpn_tests()}, + {'tlsv1', [], alpn_tests()}, + {'sslv3', [], alpn_not_supported()} + ]. + +alpn_tests() -> + [empty_protocols_are_not_allowed, + protocols_must_be_a_binary_list, + empty_client, + empty_server, + empty_client_empty_server, + no_matching_protocol, + client_alpn_and_server_alpn, + client_alpn_and_server_no_support, + client_no_support_and_server_alpn, + client_alpn_npn_and_server_alpn, + client_alpn_npn_and_server_alpn_npn, + client_alpn_and_server_alpn_npn, + client_renegotiate, + session_reused + ]. + +alpn_not_supported() -> + [alpn_not_supported_client, + alpn_not_supported_server + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl:start(), + {ok, _} = make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config)), + ssl_test_lib:cert_options(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName), + Config; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +empty_protocols_are_not_allowed(Config) when is_list(Config) -> + {error, {options, {alpn_preferred_protocols, {invalid_protocol, <<>>}}}} + = (catch ssl:listen(9443, + [{alpn_preferred_protocols, [<<"foo/1">>, <<"">>]}])), + {error, {options, {alpn_advertised_protocols, {invalid_protocol, <<>>}}}} + = (catch ssl:connect({127,0,0,1}, 9443, + [{alpn_advertised_protocols, [<<"foo/1">>, <<"">>]}])). + +%-------------------------------------------------------------------------------- + +protocols_must_be_a_binary_list(Config) when is_list(Config) -> + Option1 = {alpn_preferred_protocols, hello}, + {error, {options, Option1}} = (catch ssl:listen(9443, [Option1])), + Option2 = {alpn_preferred_protocols, [<<"foo/1">>, hello]}, + {error, {options, {alpn_preferred_protocols, {invalid_protocol, hello}}}} + = (catch ssl:listen(9443, [Option2])), + Option3 = {alpn_advertised_protocols, hello}, + {error, {options, Option3}} = (catch ssl:connect({127,0,0,1}, 9443, [Option3])), + Option4 = {alpn_advertised_protocols, [<<"foo/1">>, hello]}, + {error, {options, {alpn_advertised_protocols, {invalid_protocol, hello}}}} + = (catch ssl:connect({127,0,0,1}, 9443, [Option4])). + +%-------------------------------------------------------------------------------- + +empty_client(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +empty_server(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, []}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +empty_client_empty_server(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, []}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +no_matching_protocol(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_no_support(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [], + {error, protocol_not_negotiated}). + +%-------------------------------------------------------------------------------- + +client_no_support_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {error, protocol_not_negotiated}). + +%-------------------------------------------------------------------------------- + +client_alpn_npn_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_npn_and_server_alpn_npn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_alpn_npn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_renegotiate(Config) when is_list(Config) -> + Data = "hello world", + + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, + ExpectedProtocol = {ok, <<"http/1.0">>}, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, 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, assert_alpn_and_renegotiate_and_send_data, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%-------------------------------------------------------------------------------- + +session_reused(Config) when is_list(Config)-> + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result_msg, []}}, + {options, ClientOpts}]), + + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(?SLEEP), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + + receive + {Client1, SessionInfo} -> + ok; + {Client1, Other} -> + ct:fail(Other) + end, + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ssl_test_lib:close(Client1). + +%-------------------------------------------------------------------------------- + +alpn_not_supported_client(Config) when is_list(Config) -> + ClientOpts0 = ?config(client_opts, Config), + PrefProtocols = {client_preferred_next_protocols, + {client, [<<"http/1.0">>], <<"http/1.1">>}}, + ClientOpts = [PrefProtocols] ++ ClientOpts0, + {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, + {port, 8888}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, {error, + {options, + {not_supported_in_sslv3, PrefProtocols}}}). + +%-------------------------------------------------------------------------------- + +alpn_not_supported_server(Config) when is_list(Config)-> + ServerOpts0 = ?config(server_opts, Config), + AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + ServerOpts = [AdvProtocols] ++ ServerOpts0, + + {error, {options, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) -> + ClientOpts = ClientExtraOpts ++ ?config(client_opts, Config), + ServerOpts = ServerExtraOpts ++ ?config(server_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, placeholder, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + ExpectedResult + = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, placeholder, []}}, + {options, ClientOpts}]). + +run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> + Data = "hello world", + + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = ClientExtraOpts ++ ClientOpts0, + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = ServerExtraOpts ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, 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, ssl_send_and_assert_alpn, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +assert_alpn(Socket, Protocol) -> + ct:log("Negotiated Protocol ~p, Expecting: ~p ~n", + [ssl:negotiated_protocol(Socket), Protocol]), + Protocol = ssl:negotiated_protocol(Socket). + +assert_alpn_and_renegotiate_and_send_data(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ct:log("Renegotiating ~n", []), + ok = ssl:renegotiate(Socket), + ssl:send(Socket, Data), + assert_alpn(Socket, Protocol), + ok. + +ssl_send_and_assert_alpn(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ssl_send(Socket, Data). + +ssl_receive_and_assert_alpn(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ssl_receive(Socket, Data). + +ssl_send(Socket, Data) -> + ct:log("Connection info: ~p~n", + [ssl:connection_information(Socket)]), + ssl:send(Socket, Data). + +ssl_receive(Socket, Data) -> + ssl_receive(Socket, Data, []). + +ssl_receive(Socket, Data, Buffer) -> + ct:log("Connection info: ~p~n", + [ssl:connection_information(Socket)]), + receive + {ssl, Socket, MoreData} -> + ct:log("Received ~p~n",[MoreData]), + NewBuffer = Buffer ++ MoreData, + case NewBuffer of + Data -> + ssl:send(Socket, "Got it"), + ok; + _ -> + ssl_receive(Socket, Data, NewBuffer) + end; + Other -> + ct:fail({unexpected_message, Other}) + after 4000 -> + ct:fail({did_not_get, Data}) + end. + +connection_info_result(Socket) -> + ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index df9432a43b..f032c769e2 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2007-2015. 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/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -34,9 +35,7 @@ -include("tls_record.hrl"). -include("tls_handshake.hrl"). --define('24H_in_sec', 86400). --define(TIMEOUT, 60000). --define(LONG_TIMEOUT, 600000). +-define(TIMEOUT, 20000). -define(EXPIRE, 10). -define(SLEEP, 500). -define(RENEGOTIATION_DISABLE_TIME, 12000). @@ -45,9 +44,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, @@ -65,7 +61,7 @@ groups() -> {'tlsv1.2', [], all_versions_groups()}, {'tlsv1.1', [], all_versions_groups()}, {'tlsv1', [], all_versions_groups() ++ rizzo_tests()}, - {'sslv3', [], all_versions_groups() ++ rizzo_tests()}, + {'sslv3', [], all_versions_groups() ++ rizzo_tests() ++ [ciphersuite_vs_version]}, {api,[], api_tests()}, {session, [], session_tests()}, {renegotiate, [], renegotiate_tests()}, @@ -91,6 +87,7 @@ basic_tests() -> connect_twice, connect_dist, clear_pem_cache, + defaults, fallback ]. @@ -117,7 +114,6 @@ options_tests() -> tcp_reuseaddr, honor_server_cipher_order, honor_client_cipher_order, - ciphersuite_vs_version, unordered_protocol_versions_server, unordered_protocol_versions_client ]. @@ -132,6 +128,8 @@ api_tests() -> controlling_process, upgrade, upgrade_with_timeout, + downgrade, + close_with_timeout, shutdown, shutdown_write, shutdown_both, @@ -162,7 +160,8 @@ renegotiate_tests() -> client_no_wrap_sequence_number, server_no_wrap_sequence_number, renegotiate_dos_mitigate_active, - renegotiate_dos_mitigate_passive]. + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. cipher_tests() -> [cipher_suites, @@ -178,6 +177,9 @@ cipher_tests() -> srp_cipher_suites, srp_anon_cipher_suites, srp_dsa_cipher_suites, + rc4_rsa_cipher_suites, + rc4_ecdh_rsa_cipher_suites, + rc4_ecdsa_cipher_suites, default_reject_anonymous]. cipher_tests_ec() -> @@ -205,22 +207,17 @@ rizzo_tests() -> %%-------------------------------------------------------------------- init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), Config2 = ssl_test_lib:make_ecdsa_cert(Config1), - Config3 = ssl_test_lib:make_ecdh_rsa_cert(Config2), - Config = ssl_test_lib:cert_options(Config3), - [{watchdog, Dog} | Config] + Config = ssl_test_lib:make_ecdh_rsa_cert(Config2), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -253,6 +250,7 @@ init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client Case == unordered_protocol_versions_server-> case proplists:get_value(supported, ssl:versions()) of ['tlsv1.2' | _] -> + ct:timetrap({seconds, 5}), Config; _ -> {skip, "TLS 1.2 need but not supported on this platform"} @@ -264,15 +262,16 @@ init_per_testcase(protocol_versions, Config) -> %% For backwards compatibility sslv2 should be filtered out. application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]), ssl:start(), + ct:timetrap({seconds, 5}), Config; -init_per_testcase(reuse_session_expired, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), +init_per_testcase(reuse_session_expired, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, ?EXPIRE), application:set_env(ssl, session_delay_cleanup_time, 500), ssl:start(), + ct:timetrap({seconds, 30}), Config; init_per_testcase(empty_protocol_versions, Config) -> @@ -280,24 +279,62 @@ init_per_testcase(empty_protocol_versions, Config) -> application:load(ssl), application:set_env(ssl, protocol_version, []), ssl:start(), + ct:timetrap({seconds, 5}), Config; init_per_testcase(fallback, Config) -> case tls_record:highest_protocol_version([]) of {3, N} when N > 1 -> + ct:timetrap({seconds, 5}), Config; _ -> {skip, "Not relevant if highest supported version is less than 3.2"} end; -%% init_per_testcase(different_ca_peer_sign, Config0) -> -%% ssl_test_lib:make_mix_cert(Config0); +init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; + TestCase == server_renegotiate; + TestCase == client_secure_renegotiate; + TestCase == client_renegotiate_reused_session; + TestCase == server_renegotiate_reused_session; + TestCase == client_no_wrap_sequence_number; + TestCase == server_no_wrap_sequence_number; + TestCase == renegotiate_dos_mitigate_active; + TestCase == renegotiate_dos_mitigate_passive; + TestCase == renegotiate_dos_mitigate_absolute -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 30}), + Config; -init_per_testcase(_TestCase, Config0) -> +init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; + TestCase == psk_with_hint_cipher_suites; + TestCase == ciphers_rsa_signed_certs; + TestCase == ciphers_rsa_signed_certs_openssl_names; + TestCase == versions_option, + TestCase == tcp_connect_big -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. + + ct:timetrap({seconds, 30}), + Config; +init_per_testcase(rizzo, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 40}), + Config; + +init_per_testcase(TestCase, Config) when TestCase == ssl_accept_timeout; + TestCase == client_closes_socket; + TestCase == downgrade -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 15}), + Config; +init_per_testcase(clear_pem_cache, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 20}), + Config; + +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 5}), + Config. end_per_testcase(reuse_session_expired, Config) -> application:unset_env(ssl, session_lifetime), @@ -347,7 +384,7 @@ alerts(Config) when is_list(Config) -> end, Alerts). %%-------------------------------------------------------------------- new_options_in_accept() -> - [{doc,"Test that you can set ssl options in ssl_accept/3 and not tcp upgrade"}]. + [{doc,"Test that you can set ssl options in ssl_accept/3 and not only in tcp upgrade"}]. new_options_in_accept(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts0 = ?config(server_dsa_opts, Config), @@ -365,7 +402,9 @@ new_options_in_accept(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {?MODULE, connection_info_result, []}}, - {options, [{versions, [sslv3]} | ClientOpts]}]), + {options, [{versions, [sslv3]}, + {ciphers,[{rsa,rc4_128,sha} + ]} | ClientOpts]}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), @@ -379,7 +418,7 @@ new_options_in_accept(Config) when is_list(Config) -> %%-------------------------------------------------------------------- connection_info() -> - [{doc,"Test the API function ssl:connection_info/1"}]. + [{doc,"Test the API function ssl:connection_information/1"}]. connection_info(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), @@ -395,7 +434,7 @@ connection_info(Config) when is_list(Config) -> {from, self()}, {mfa, {?MODULE, connection_info_result, []}}, {options, - [{ciphers,[{rsa,rc4_128,sha,no_export}]} | + [{ciphers,[{rsa,des_cbc,sha,no_export}]} | ClientOpts]}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", @@ -404,7 +443,7 @@ connection_info(Config) when is_list(Config) -> Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - ServerMsg = ClientMsg = {ok, {Version, {rsa,rc4_128,sha}}}, + ServerMsg = ClientMsg = {ok, {Version, {rsa, des_cbc, sha}}}, ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), @@ -633,7 +672,7 @@ clear_pem_cache(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - [_,FilRefDb, _] = element(5, State), + [_,FilRefDb |_] = element(6, State), {Server, Client} = basic_verify_test_no_close(Config), 2 = ets:info(FilRefDb, size), ssl:clear_pem_cache(), @@ -1371,6 +1410,53 @@ upgrade_with_timeout(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +downgrade() -> + [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. +downgrade(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_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, tls_downgrade, []}}, + {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, tls_downgrade, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +close_with_timeout() -> + [{doc,"Test normal (not downgrade) ssl:close/2"}]. +close_with_timeout(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_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, tls_close, []}}, + {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, tls_close, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +%%-------------------------------------------------------------------- tcp_connect() -> [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. @@ -1408,6 +1494,7 @@ tcp_connect_big(Config) when is_list(Config) -> {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], + Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {timeout, 5000}, @@ -1419,7 +1506,6 @@ tcp_connect_big(Config) when is_list(Config) -> {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), gen_tcp:send(Socket, <<?BYTE(0), ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), @@ -1811,6 +1897,32 @@ srp_dsa_cipher_suites(Config) when is_list(Config) -> Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Ciphers = ssl_test_lib:srp_dss_suites(), run_suites(Ciphers, Version, Config, srp_dsa). +%%------------------------------------------------------------------- +rc4_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_rsa). +%------------------------------------------------------------------- +rc4_ecdh_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_ecdh_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_ecdh_rsa). + +%%------------------------------------------------------------------- +rc4_ecdsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_ecdsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_ecdsa). + %%-------------------------------------------------------------------- default_reject_anonymous()-> [{doc,"Test that by default anonymous cipher suites are rejected "}]. @@ -2371,7 +2483,7 @@ der_input(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - [CADb | _] = element(5, State), + [CADb | _] = element(6, State), [] = ets:tab2list(CADb). %%-------------------------------------------------------------------- @@ -2539,6 +2651,16 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl_test_lib:close(Client1). %%-------------------------------------------------------------------- +defaults(Config) when is_list(Config)-> + [_, + {supported, Supported}, + {available, Available}] + = ssl:versions(), + true = lists:member(sslv3, Available), + false = lists:member(sslv3, Supported), + false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), + true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)). +%%-------------------------------------------------------------------- reuseaddr() -> [{doc,"Test reuseaddr option"}]. @@ -2663,6 +2785,8 @@ honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +ciphersuite_vs_version() -> + [{doc,"Test a SSLv3 client can not negotiate a TLSv* cipher suite."}]. ciphersuite_vs_version(Config) when is_list(Config) -> {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2788,7 +2912,7 @@ listen_socket(Config) -> {error, enotconn} = ssl:send(ListenSocket, <<"data">>), {error, enotconn} = ssl:recv(ListenSocket, 0), - {error, enotconn} = ssl:connection_info(ListenSocket), + {error, enotconn} = ssl:connection_information(ListenSocket), {error, enotconn} = ssl:peername(ListenSocket), {error, enotconn} = ssl:peercert(ListenSocket), {error, enotconn} = ssl:session_info(ListenSocket), @@ -2955,8 +3079,36 @@ renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +renegotiate_dos_mitigate_absolute() -> + [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. +renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{client_renegotiation, 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, + renegotiate_rejected, + []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- tcp_error_propagation_in_active_mode() -> - [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. + [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error occurs"}]. tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), @@ -3390,23 +3542,43 @@ renegotiate_reuse_session(Socket, Data) -> renegotiate(Socket, Data). renegotiate_immediately(Socket) -> - receive + receive {ssl, Socket, "Hello world"} -> ok; %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast {ssl, Socket, "H"} -> - receive + receive {ssl, Socket, "ello world"} -> ok end end, ok = ssl:renegotiate(Socket), {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), ok = ssl:renegotiate(Socket), ct:log("Renegotiated again"), ssl:send(Socket, "Hello world"), ok. + +renegotiate_rejected(Socket) -> + receive + {ssl, Socket, "Hello world"} -> + ok; + %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast + {ssl, Socket, "H"} -> + receive + {ssl, Socket, "ello world"} -> + ok + end + end, + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:log("Failed to renegotiate again"), + ssl:send(Socket, "Hello world"), + ok. + new_config(PrivDir, ServerOpts0) -> CaCertFile = proplists:get_value(cacertfile, ServerOpts0), @@ -3726,8 +3898,20 @@ run_suites(Ciphers, Version, Config, Type) -> ?config(server_ecdsa_opts, Config)}; ecdh_rsa -> {?config(client_opts, Config), - ?config(server_ecdh_rsa_opts, Config)} - end, + ?config(server_ecdh_rsa_opts, Config)}; + rc4_rsa -> + {?config(client_opts, Config), + [{ciphers, Ciphers} | + ?config(server_opts, Config)]}; + rc4_ecdh_rsa -> + {?config(client_opts, Config), + [{ciphers, Ciphers} | + ?config(server_ecdh_rsa_opts, Config)]}; + rc4_ecdsa -> + {?config(client_opts, Config), + [{ciphers, Ciphers} | + ?config(server_ecdsa_opts, Config)]} + end, Result = lists:map(fun(Cipher) -> cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, @@ -3748,6 +3932,7 @@ erlang_cipher_suite(Suite) -> cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> %% process_flag(trap_exit, true), ct:log("Testing CipherSuite ~p~n", [CipherSuite]), + ct:log("Server Opts ~p~n", [ServerOpts]), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), ErlangCipherSuite = erlang_cipher_suite(CipherSuite), @@ -3780,10 +3965,10 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> end. connection_info_result(Socket) -> - ssl:connection_info(Socket). - + {ok, Info} = ssl:connection_information(Socket, [protocol, cipher_suite]), + {ok, {proplists:get_value(protocol, Info), proplists:get_value(cipher_suite, Info)}}. version_info_result(Socket) -> - {ok, {Version, _}} = ssl:connection_info(Socket), + {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. connect_dist_s(S) -> @@ -3795,6 +3980,33 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. +tls_downgrade(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + case ssl:close(Socket, {self(), 10000}) of + {ok, TCPSocket} -> + inet:setopts(TCPSocket, [{active, true}]), + gen_tcp:send(TCPSocket, "Downgraded"), + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:pal("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; + {error, timeout} -> + ct:pal("Timed out, downgrade aborted"), + ok; + Fail -> + {error, Fail} + end. + +tls_close(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + ok = ssl:close(Socket, 5000). + + %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast treashold(N, {3,0}) -> (N div 2) + 1; diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec new file mode 100644 index 0000000000..d2f75b4203 --- /dev/null +++ b/lib/ssl/test/ssl_bench.spec @@ -0,0 +1 @@ +{suites,"../ssl_test",[ssl_bench_SUITE]}. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl new file mode 100644 index 0000000000..953356c87c --- /dev/null +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -0,0 +1,367 @@ +%%%------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ssl_bench_SUITE). +-compile(export_all). +-include_lib("common_test/include/ct_event.hrl"). + +-define(remote_host, "NETMARKS_REMOTE_HOST"). + +suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. + +all() -> [{group, setup}, {group, payload}]. + +groups() -> + [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, + {payload, [{repeat, 3}], [payload_simple]} + ]. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_suite(Config) -> + try + Server = setup(ssl, node()), + [{server_node, Server}|Config] + catch _:_ -> + {skipped, "Benchmark machines only"} + end. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_Func, Conf) -> + Conf. + +end_per_testcase(_Func, _Conf) -> + ok. + + +-define(COUNT, 400). +-define(TC(Cmd), tc(fun() -> Cmd end, ?MODULE, ?LINE)). + +-define(FPROF_CLIENT, false). +-define(FPROF_SERVER, false). +-define(EPROF_CLIENT, false). +-define(EPROF_SERVER, false). +-define(PERCEPT_SERVER, false). + +%% Current numbers gives roughly a testcase per minute on todays hardware.. + +setup_sequential(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, setup_connection, ?COUNT * 20, 1, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Sequential setup"}]}), + ok. + +setup_concurrent(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, setup_connection, ?COUNT, 100, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Concurrent setup"}]}), + ok. + +payload_simple(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, payload, ?COUNT*300, 10, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Payload simple"}]}), + ok. + + +ssl() -> + test(ssl, ?COUNT, node()). + +test(Type, Count, Host) -> + Server = setup(Type, Host), + (do_test(Type, setup_connection, Count * 20, 1, Server)), + (do_test(Type, setup_connection, Count, 100, Server)), + (do_test(Type, payload, Count*300, 10, Server)), + ok. + +do_test(Type, TC, Loop, ParallellConnections, Server) -> + _ = ssl:stop(), + {ok, _} = ensure_all_started(ssl, []), + + {ok, {SPid, Host, Port}} = rpc:call(Server, ?MODULE, setup_server_init, + [Type, TC, Loop, ParallellConnections]), + link(SPid), + Me = self(), + Test = fun(Id) -> + CData = client_init(Me, Type, TC, Host, Port), + receive + go -> + ?FPROF_CLIENT andalso Id =:= 1 andalso + start_profile(fprof, [self(),new]), + ?EPROF_CLIENT andalso Id =:= 1 andalso + start_profile(eprof, [ssl_connection_sup, ssl_manager]), + ok = ?MODULE:TC(Loop, Type, CData), + ?FPROF_CLIENT andalso Id =:= 1 andalso + stop_profile(fprof, "test_connection_client_res.fprof"), + ?EPROF_CLIENT andalso Id =:= 1 andalso + stop_profile(eprof, "test_connection_client_res.eprof"), + Me ! self() + end + end, + Spawn = fun(Id) -> + Pid = spawn(fun() -> Test(Id) end), + receive {Pid, init} -> Pid end + end, + Pids = [Spawn(Id) || Id <- lists:seq(ParallellConnections, 1, -1)], + Run = fun() -> + [Pid ! go || Pid <- Pids], + [receive Pid -> ok end || Pid <- Pids] + end, + {TimeInMicro, _} = timer:tc(Run), + TotalTests = ParallellConnections * Loop, + TestPerSecond = 1000000 * TotalTests div TimeInMicro, + io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]), + unlink(SPid), + SPid ! quit, + {ok, TestPerSecond}. + +server_init(ssl, setup_connection, _, _, Server) -> + {ok, Socket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, Host} = inet:gethostname(), + ?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]), + %%?EPROF_SERVER andalso start_profile(eprof, [ssl_connection_sup, ssl_manager]), + ?EPROF_SERVER andalso start_profile(eprof, [ssl_manager]), + ?PERCEPT_SERVER andalso percept:profile("/tmp/ssl_server.percept"), + Server ! {self(), {init, Host, Port}}, + Test = fun(TSocket) -> + ok = ssl:ssl_accept(TSocket), + ssl:close(TSocket) + end, + setup_server_connection(Socket, Test); +server_init(ssl, payload, Loop, _, Server) -> + {ok, Socket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, Host} = inet:gethostname(), + Server ! {self(), {init, Host, Port}}, + Test = fun(TSocket) -> + ok = ssl:ssl_accept(TSocket), + Size = byte_size(msg()), + server_echo(TSocket, Size, Loop), + ssl:close(TSocket) + end, + setup_server_connection(Socket, Test); + +server_init(Type, Tc, _, _, Server) -> + io:format("No server init code for ~p ~p~n",[Type, Tc]), + Server ! {self(), no_init}. + +client_init(Master, ssl, setup_connection, Host, Port) -> + Master ! {self(), init}, + {Host, Port, ssl_opts(connect)}; +client_init(Master, ssl, payload, Host, Port) -> + {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect)), + Master ! {self(), init}, + Size = byte_size(msg()), + {Sock, Size}; +client_init(_Me, Type, Tc, Host, Port) -> + io:format("No client init code for ~p ~p~n",[Type, Tc]), + {Host, Port}. + +setup_server_connection(LSocket, Test) -> + receive quit -> + ?FPROF_SERVER andalso stop_profile(fprof, "test_server_res.fprof"), + ?EPROF_SERVER andalso stop_profile(eprof, "test_server_res.eprof"), + ?PERCEPT_SERVER andalso stop_profile(percept, "/tmp/ssl_server.percept"), + ok + after 0 -> + case ssl:transport_accept(LSocket, 2000) of + {ok, TSocket} -> spawn_link(fun() -> Test(TSocket) end); + {error, timeout} -> ok + end, + setup_server_connection(LSocket, Test) + end. + +server_echo(Socket, Size, Loop) when Loop > 0 -> + {ok, Msg} = ssl:recv(Socket, Size), + ok = ssl:send(Socket, Msg), + server_echo(Socket, Size, Loop-1); +server_echo(_, _, _) -> ok. + +setup_connection(N, ssl, Env = {Host, Port, Opts}) when N > 0 -> + case ssl:connect(Host, Port, Opts) of + {ok, Sock} -> + ssl:close(Sock), + setup_connection(N-1, ssl, Env); + {error, Error} -> + io:format("Error: ~p (~p)~n",[Error, length(erlang:ports())]), + setup_connection(N, ssl, Env) + end; +setup_connection(_, _, _) -> + ok. + +payload(Loop, ssl, D = {Socket, Size}) when Loop > 0 -> + ok = ssl:send(Socket, msg()), + {ok, _} = ssl:recv(Socket, Size), + payload(Loop-1, ssl, D); +payload(_, _, {Socket, _}) -> + ssl:close(Socket). + +msg() -> + <<"Hello", + 0:(512*8), + "asdlkjsafsdfoierwlejsdlkfjsdf", + 1:(512*8), + "asdlkjsafsdfoierwlejsdlkfjsdf">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +setup(_Type, nonode@nohost) -> + exit(dist_not_enabled); +setup(Type, _This) -> + Host = case os:getenv(?remote_host) of + false -> + {ok, This} = inet:gethostname(), + This; + RemHost -> + RemHost + end, + Node = list_to_atom("perf_server@" ++ Host), + SlaveArgs = case init:get_argument(pa) of + {ok, PaPaths} -> + lists:append([" -pa " ++ P || [P] <- PaPaths]); + _ -> [] + end, + %% io:format("Slave args: ~p~n",[SlaveArgs]), + Prog = + case os:find_executable("erl") of + false -> "erl"; + P -> P + end, + io:format("Prog = ~p~n", [Prog]), + + case net_adm:ping(Node) of + pong -> ok; + pang -> + {ok, Node} = slave:start(Host, perf_server, SlaveArgs, no_link, Prog) + end, + Path = code:get_path(), + true = rpc:call(Node, code, set_path, [Path]), + ok = rpc:call(Node, ?MODULE, setup_server, [Type, node()]), + io:format("Client (~p) using ~s~n",[node(), code:which(ssl)]), + (Node =:= node()) andalso restrict_schedulers(client), + Node. + +setup_server(_Type, ClientNode) -> + (ClientNode =:= node()) andalso restrict_schedulers(server), + io:format("Server (~p) using ~s~n",[node(), code:which(ssl)]), + ok. + + +ensure_all_started(App, Ack) -> + case application:start(App) of + ok -> {ok, [App|Ack]}; + {error, {not_started, Dep}} -> + {ok, Ack1} = ensure_all_started(Dep, Ack), + ensure_all_started(App, Ack1); + {error, {already_started, _}} -> + {ok, Ack} + end. + +setup_server_init(Type, Tc, Loop, PC) -> + _ = ssl:stop(), + {ok, _} = ensure_all_started(ssl, []), + Me = self(), + Pid = spawn_link(fun() -> server_init(Type, Tc, Loop, PC, Me) end), + Res = receive + {Pid, {init, Host, Port}} -> {ok, {Pid, Host, Port}}; + {Pid, Error} -> {error, Error} + end, + unlink(Pid), + Res. + +restrict_schedulers(Type) -> + %% We expect this to run on 8 core machine + Extra0 = 1, + Extra = if (Type =:= server) -> -Extra0; true -> Extra0 end, + Scheds = erlang:system_info(schedulers), + erlang:system_flag(schedulers_online, (Scheds div 2) + Extra). + +tc(Fun, Mod, Line) -> + case timer:tc(Fun) of + {_,{'EXIT',Reason}} -> + io:format("Process EXITED ~p:~p \n", [Mod, Line]), + exit(Reason); + {_T,R={error,_}} -> + io:format("Process Error ~p:~p \n", [Mod, Line]), + R; + {T,R} -> + io:format("~p:~p: Time: ~p\n", [Mod, Line, T]), + R + end. + +start_profile(eprof, Procs) -> + profiling = eprof:start_profiling(Procs), + io:format("(E)Profiling ...",[]); +start_profile(fprof, Procs) -> + fprof:trace([start, {procs, Procs}]), + io:format("(F)Profiling ...",[]). + +stop_profile(percept, File) -> + percept:stop_profile(), + percept:analyze(File), + {started, _Host, Port} = percept:start_webserver(), + wx:new(), + wx_misc:launchDefaultBrowser("http://" ++ net_adm:localhost() ++ ":" ++ integer_to_list(Port)), + ok; +stop_profile(eprof, File) -> + profiling_stopped = eprof:stop_profiling(), + eprof:log(File), + io:format(".analysed => ~s ~n",[File]), + eprof:analyze(total), + eprof:stop(); +stop_profile(fprof, File) -> + fprof:trace(stop), + io:format("..collect..",[]), + fprof:profile(), + fprof:analyse([{dest, File},{totals, true}]), + io:format(".analysed => ~s ~n",[File]), + fprof:stop(), + ok. + +ssl_opts(listen) -> + [{backlog, 500} | ssl_opts("server")]; +ssl_opts(connect) -> + [{verify, verify_peer} + | ssl_opts("client")]; +ssl_opts(Role) -> + Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]), + [{active, false}, + {depth, 2}, + {reuseaddr, true}, + {mode,binary}, + {nodelay, true}, + {ciphers, [{dhe_rsa,aes_256_cbc,sha}]}, + {cacertfile, filename:join([Dir, Role, "cacerts.pem"])}, + {certfile, filename:join([Dir, Role, "cert.pem"])}, + {keyfile, filename:join([Dir, Role, "key.pem"])}]. diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index dab7a941db..5940a86a7f 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012-2014. 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/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -37,9 +38,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, active}, {group, passive}, @@ -80,20 +78,15 @@ error_handling_tests()-> no_authority_key_identifier]. init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - - Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config = ssl_test_lib:cert_options(Config1), - [{watchdog, Dog} | Config] + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), + Config = ssl_test_lib:make_dsa_cert(Config0), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -114,6 +107,14 @@ init_per_group(_, Config) -> end_per_group(_GroupName, Config) -> Config. +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index 0e48b674e0..b8096c5d7a 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,14 +30,9 @@ -include("ssl_cipher.hrl"). -include("ssl_alert.hrl"). --define(TIMEOUT, 600000). - %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [aes_decipher_good, aes_decipher_fail, padding_test]. @@ -61,10 +57,9 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. end_per_testcase(_TestCase, Config) -> Config. @@ -84,13 +79,11 @@ aes_decipher_good(Config) when is_list(Config) -> decipher_check_good(HashSz, CipherState, {3,3}). %%-------------------------------------------------------------------- - aes_decipher_fail() -> [{doc,"Decipher a known cryptotext using a incorrect key"}]. aes_decipher_fail(Config) when is_list(Config) -> HashSz = 32, - CipherState = incorrect_cipher_state(), decipher_check_fail(HashSz, CipherState, {3,0}), decipher_check_fail(HashSz, CipherState, {3,1}), @@ -110,37 +103,37 @@ padding_test(Config) when is_list(Config) -> % Internal functions -------------------------------------------------------- %%-------------------------------------------------------------------- decipher_check_good(HashSz, CipherState, Version) -> - {Content, NextIV, Mac} = content_nextiv_mac(Version), - {Content, Mac, #cipher_state{iv = NextIV}} = - ssl_cipher:decipher(?AES, HashSz, CipherState, aes_fragment(Version), Version, true). + {Content, _NextIV, Mac} = content_nextiv_mac(Version), + {Content, Mac, _} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true). decipher_check_fail(HashSz, CipherState, Version) -> {Content, NextIV, Mac} = content_nextiv_mac(Version), true = {Content, Mac, #cipher_state{iv = NextIV}} =/= - ssl_cipher:decipher(?AES, HashSz, CipherState, aes_fragment(Version), Version, true). + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true). pad_test(HashSz, CipherState, {3,0} = Version) -> %% 3.0 does not have padding test {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), {Content, Mac, #cipher_state{iv = NextIV}} = - ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true), + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true), {Content, Mac, #cipher_state{iv = NextIV}} = - ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false); + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false); pad_test(HashSz, CipherState, {3,1} = Version) -> %% 3.1 should have padding test, but may be disabled {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), BadCont = badpad_content(Content), {Content, Mac, #cipher_state{iv = NextIV}} = - ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false), + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false), {BadCont, Mac, #cipher_state{iv = NextIV}} = - ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true); + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true); pad_test(HashSz, CipherState, Version) -> %% 3.2 and 3.3 must have padding test {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), BadCont = badpad_content(Content), - {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, + {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(Version), Version, false), - {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, + {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(Version), Version, true). aes_fragment({3,N}) when N == 0; N == 1-> @@ -164,7 +157,7 @@ badpad_aes_fragment(_) -> content_nextiv_mac({3,N}) when N == 0; N == 1 -> {<<"HELLO\n">>, - <<33,0, 177,251, 91,44, 247,53, 183,198, 165,63, 20,194, 159,107>>, + <<72,196,247,97,62,213,222,109,210,204,217,186,172,184, 197,148>>, <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}; content_nextiv_mac(_) -> {<<"HELLO\n">>, @@ -193,3 +186,4 @@ correct_cipher_state() -> incorrect_cipher_state() -> #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>}. + diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index bad0949ec4..44580be1ff 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,54 +27,41 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). --define(TIMEOUT, 120000). --define(LONG_TIMEOUT, 600000). --define(SLEEP, 1000). --define(OPENSSL_RENEGOTIATE, "R\n"). --define(OPENSSL_QUIT, "Q\n"). --define(OPENSSL_GARBAGE, "P\n"). --define(EXPIRE, 10). - %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ - {group, basic}, - {group, v1_crl}, - {group, idp_crl} + {group, check_true}, + {group, check_peer}, + {group, check_best_effort} ]. groups() -> - [{basic, [], basic_tests()}, - {v1_crl, [], v1_crl_tests()}, - {idp_crl, [], idp_crl_tests()}]. + [ + {check_true, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}]}, + {check_peer, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}]}, + {check_best_effort, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}]}, + {v2_crl, [], basic_tests()}, + {v1_crl, [], basic_tests()}, + {idp_crl, [], basic_tests()}]. basic_tests() -> [crl_verify_valid, crl_verify_revoked]. -v1_crl_tests() -> - [crl_verify_valid, crl_verify_revoked]. -idp_crl_tests() -> - [crl_verify_valid, crl_verify_revoked]. - -%%%================================================================ -%%% Suite init/end - -init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), +init_per_suite(Config) -> case os:find_executable("openssl") of false -> {skip, "Openssl not found"}; _ -> - TLSVersion = ?config(tls_version, Config0), OpenSSL_version = (catch os:cmd("openssl version")), - ct:log("TLS version: ~p~nOpenSSL version: ~p~n~n~p:module_info(): ~p~n~nssl:module_info(): ~p~n", - [TLSVersion, OpenSSL_version, ?MODULE, ?MODULE:module_info(), ssl:module_info()]), case ssl_test_lib:enough_openssl_crl_support(OpenSSL_version) of false -> {skip, io_lib:format("Bad openssl version: ~p",[OpenSSL_version])}; @@ -81,16 +69,14 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), {ok, Hostname0} = inet:gethostname(), IPfamily = case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts,[])) of true -> inet6; false -> inet end, - [{ipfamily,IPfamily}, {watchdog, Dog}, {openssl_version,OpenSSL_version} | Config0] - catch _C:_E -> - ct:log("crypto:start() caught ~p:~p",[_C,_E]), + [{ipfamily,IPfamily}, {openssl_version,OpenSSL_version} | Config] + catch _:_ -> {skip, "Crypto did not start"} end end @@ -100,443 +86,176 @@ end_per_suite(_Config) -> ssl:stop(), application:stop(crypto). -%%%================================================================ -%%% Group init/end - -init_per_group(Group, Config) -> - ssl:start(), - inets:start(), - CertDir = filename:join(?config(priv_dir, Config), Group), - DataDir = ?config(data_dir, Config), - ServerRoot = make_dir_path([?config(priv_dir,Config), Group, tmp]), - %% start a HTTP server to serve the CRLs - {ok, Httpd} = inets:start(httpd, [{ipfamily, ?config(ipfamily,Config)}, - {server_name, "localhost"}, {port, 0}, - {server_root, ServerRoot}, - {document_root, CertDir}, - {modules, [mod_get]} - ]), - [{port,Port}] = httpd:info(Httpd, [port]), - ct:log("~p:~p~nHTTPD IP family=~p, port=~p~n", [?MODULE, ?LINE, ?config(ipfamily,Config), Port]), - CertOpts = [{crl_port,Port}|cert_opts(Group)], - Result = make_certs:all(DataDir, CertDir, CertOpts), - ct:log("~p:~p~nmake_certs:all(~n DataDir=~p,~n CertDir=~p,~n ServerRoot=~p~n Opts=~p~n) returned ~p~n", [?MODULE,?LINE,DataDir, CertDir, ServerRoot, CertOpts, Result]), - [{make_cert_result, Result}, {cert_dir, CertDir}, {httpd, Httpd} | Config]. - -cert_opts(v1_crl) -> [{v2_crls, false}]; -cert_opts(idp_crl) -> [{issuing_distribution_point, true}]; -cert_opts(_) -> []. - -make_dir_path(PathComponents) -> - lists:foldl(fun(F,P0) -> file:make_dir(P=filename:join(P0,F)), P end, - "", - PathComponents). - +init_per_group(check_true, Config) -> + [{crl_check, true} | Config]; +init_per_group(check_peer, Config) -> + [{crl_check, peer} | Config]; +init_per_group(check_best_effort, Config) -> + [{crl_check, best_effort} | Config]; +init_per_group(Group, Config0) -> + case is_idp(Group) of + true -> + [{idp_crl, true} | Config0]; + false -> + DataDir = ?config(data_dir, Config0), + CertDir = filename:join(?config(priv_dir, Config0), Group), + {CertOpts, Config} = init_certs(CertDir, Group, Config0), + {ok, _} = make_certs:all(DataDir, CertDir, CertOpts), + [{cert_dir, CertDir}, {idp_crl, false} | Config] + end. end_per_group(_GroupName, Config) -> - case ?config(httpd, Config) of - undefined -> ok; - Pid -> - ct:log("Stop httpd ~p",[Pid]), - ok = inets:stop(httpd, Pid) - ,ct:log("Stopped",[]) - end, - inets:stop(), + Config. +init_per_testcase(Case, Config0) -> + case ?config(idp_crl, Config0) of + true -> + end_per_testcase(Case, Config0), + inets:start(), + ssl:start(), + ServerRoot = make_dir_path([?config(priv_dir, Config0), idp_crl, tmp]), + %% start a HTTP server to serve the CRLs + {ok, Httpd} = inets:start(httpd, [{ipfamily, ?config(ipfamily, Config0)}, + {server_name, "localhost"}, {port, 0}, + {server_root, ServerRoot}, + {document_root, + filename:join(?config(priv_dir, Config0), idp_crl)} + ]), + [{port,Port}] = httpd:info(Httpd, [port]), + Config = [{httpd_port, Port} | Config0], + DataDir = ?config(data_dir, Config), + CertDir = filename:join(?config(priv_dir, Config0), idp_crl), + {CertOpts, Config} = init_certs(CertDir, idp_crl, Config), + {ok, _} = make_certs:all(DataDir, CertDir, CertOpts), + ct:timetrap({seconds, 6}), + [{cert_dir, CertDir} | Config]; + false -> + end_per_testcase(Case, Config0), + ssl:start(), + Config0 + end. + +end_per_testcase(_, Config) -> + case ?config(idp_crl, Config) of + true -> + ssl:stop(), + inets:stop(); + false -> + ssl:stop() + end. + %%%================================================================ %%% Test cases +%%%================================================================ crl_verify_valid() -> [{doc,"Verify a simple valid CRL chain"}]. crl_verify_valid(Config) when is_list(Config) -> - process_flag(trap_exit, true), PrivDir = ?config(cert_dir, Config), - ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])}, - {certfile, filename:join([PrivDir, "server", "cert.pem"])}, - {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}], - + Check = ?config(crl_check, Config), + ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])}, + {certfile, filename:join([PrivDir, "server", "cert.pem"])}, + {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}], + ClientOpts = case ?config(idp_crl, Config) of + true -> + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}, + {verify, verify_peer}]; + false -> + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}] + end, {ClientNode, ServerNode, Hostname} = 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]}}, - %{mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - ct:log("~p:~p~nreturn from ssl_test_lib:start_server:~n~p",[?MODULE,?LINE,Server]), - Port = ssl_test_lib:inet_port(Server), - CACerts = load_cert(filename:join([PrivDir, "erlangCA", "cacerts.pem"])), - - ClientOpts = [{cacerts, CACerts}, - {verify, verify_peer}, - {verify_fun, {fun validate_function/3, {CACerts, []}}}], - - - ct:log("~p:~p~ncalling ssl_test_lib:start_client",[?MODULE,?LINE]), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_send, [Data]}}, - %{mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ct:log("~p:~p~nreturn from ssl_test_lib:start_client:~n~p",[?MODULE,?LINE,Client]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). + ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}), + ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}), + + crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts). crl_verify_revoked() -> - [{doc,"Verify a simple valid CRL chain"}]. -crl_verify_revoked(Config) when is_list(Config) -> - process_flag(trap_exit, true), + [{doc,"Verify a simple CRL chain when peer cert is reveoked"}]. +crl_verify_revoked(Config) when is_list(Config) -> PrivDir = ?config(cert_dir, Config), + Check = ?config(crl_check, Config), ServerOpts = [{keyfile, filename:join([PrivDir, "revoked", "key.pem"])}, - {certfile, filename:join([PrivDir, "revoked", "cert.pem"])}, - {cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}], - ct:log("~p:~p~nserver opts ~p~n", [?MODULE,?LINE, ServerOpts]), + {certfile, filename:join([PrivDir, "revoked", "cert.pem"])}, + {cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - %{mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), + + ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}), + ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}), + + ClientOpts = case ?config(idp_crl, Config) of + true -> + [{cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}, + {crl_check, Check}, + {verify, verify_peer}]; + false -> + [{cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}] + end, + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + receive + {Server, AlertOrColse} -> + ct:pal("Server Alert or Close ~p", [AlertOrColse]) + end, + ssl_test_lib:check_result(Client, {error, {tls_alert, "certificate revoked"}}). - CACerts = load_cert(filename:join([PrivDir, "erlangCA", "cacerts.pem"])), - ClientOpts = [{cacerts, CACerts}, - {verify, verify_peer}, - {verify_fun, {fun validate_function/3, {CACerts, []}}}], - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, +crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts) -> + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - %{mfa, {?MODULE, - %erlang_ssl_receive, [Data]}}, - {mfa, {ssl_test_lib, no_result, []}}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), - %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - process_flag(trap_exit, false). - -%%%================================================================ -%%% Lib - -erlang_ssl_receive(Socket, Data) -> - ct:log("~p:~p~nConnection info: ~p~n", - [?MODULE,?LINE, ssl:connection_info(Socket)]), - receive - {ssl, Socket, Data} -> - ct:log("~p:~p~nReceived ~p~n",[?MODULE,?LINE, Data]), - %% open_ssl server sometimes hangs waiting in blocking read - ssl:send(Socket, "Got it"), - ok; - {ssl, Socket, Byte} when length(Byte) == 1 -> - erlang_ssl_receive(Socket, tl(Data)); - {Port, {data,Debug}} when is_port(Port) -> - ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), - erlang_ssl_receive(Socket,Data); - Other -> - ct:fail({unexpected_message, Other}) - after 4000 -> - ct:fail({did_not_get, Data}) - end. - - -erlang_ssl_send(Socket, Data) -> - ct:log("~p:~p~nConnection info: ~p~n", - [?MODULE,?LINE, ssl:connection_info(Socket)]), - ssl:send(Socket, Data), - ok. - -load_certs(undefined) -> - undefined; -load_certs(CertDir) -> - case file:list_dir(CertDir) of - {ok, Certs} -> - load_certs(lists:map(fun(Cert) -> filename:join(CertDir, Cert) - end, Certs), []); - {error, _} -> - undefined - end. - -load_certs([], Acc) -> - ct:log("~p:~p~nSuccessfully loaded ~p CA certificates~n", [?MODULE,?LINE, length(Acc)]), - Acc; -load_certs([Cert|Certs], Acc) -> - case filelib:is_dir(Cert) of - true -> - load_certs(Certs, Acc); - _ -> - %ct:log("~p:~p~nLoading certificate ~p~n", [?MODULE,?LINE, Cert]), - load_certs(Certs, load_cert(Cert) ++ Acc) - end. - -load_cert(Cert) -> - {ok, Bin} = file:read_file(Cert), - case filename:extension(Cert) of - ".der" -> - %% no decoding necessary - [Bin]; - _ -> - %% assume PEM otherwise - Contents = public_key:pem_decode(Bin), - [DER || {Type, DER, Cipher} <- Contents, Type == 'Certificate', Cipher == 'not_encrypted'] - end. - -%% @doc Validator function for SSL negotiation. -%% -validate_function(Cert, valid_peer, State) -> - ct:log("~p:~p~nvaliding peer ~p with ~p intermediate certs~n", - [?MODULE,?LINE, get_common_name(Cert), - length(element(2, State))]), - %% peer certificate validated, now check the CRL - Res = (catch check_crl(Cert, State)), - ct:log("~p:~p~nCRL validate result for ~p: ~p~n", - [?MODULE,?LINE, get_common_name(Cert), Res]), - {Res, State}; -validate_function(Cert, valid, {TrustedCAs, IntermediateCerts}=State) -> - case public_key:pkix_is_self_signed(Cert) of - true -> - ct:log("~p:~p~nroot certificate~n",[?MODULE,?LINE]), - %% this is a root cert, no CRL - {valid, {TrustedCAs, [Cert|IntermediateCerts]}}; - false -> - %% check is valid CA certificate, add to the list of - %% intermediates - Res = (catch check_crl(Cert, State)), - ct:log("~p:~p~nCRL intermediate CA validate result for ~p: ~p~n", - [?MODULE,?LINE, get_common_name(Cert), Res]), - {Res, {TrustedCAs, [Cert|IntermediateCerts]}} - end; -validate_function(_Cert, _Event, State) -> - %ct:log("~p:~p~nignoring event ~p~n", [?MODULE,?LINE, _Event]), - {valid, State}. - -%% @doc Given a certificate, find CRL distribution points for the given -%% certificate, fetch, and attempt to validate each CRL through -%% issuer_function/4. -%% -check_crl(Cert, State) -> - %% pull the CRL distribution point(s) out of the certificate, if any - ct:log("~p:~p~ncheck_crl(~n Cert=~p,~nState=~p~n)",[?MODULE,?LINE,Cert,State]), - case pubkey_cert:select_extension( - ?'id-ce-cRLDistributionPoints', - pubkey_cert:extensions_list(Cert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.extensions)) of - undefined -> - ct:log("~p:~p~nno CRL distribution points for ~p~n", - [?MODULE,?LINE, get_common_name(Cert)]), - %% fail; we can't validate if there's no CRL - no_crl; - CRLExtension -> - ct:log("~p:~p~nCRLExtension=~p)",[?MODULE,?LINE,CRLExtension]), - CRLDistPoints = CRLExtension#'Extension'.extnValue, - DPointsAndCRLs = lists:foldl(fun(Point, Acc) -> - %% try to read the CRL over http or from a - %% local file - case fetch_point(Point) of - not_available -> - ct:log("~p:~p~nfetch_point returned~n~p~n)",[?MODULE,?LINE,not_available]), - Acc; - Res -> - ct:log("~p:~p~nfetch_point returned~n~p~n)",[?MODULE,?LINE,Res]), - [{Point, Res} | Acc] - end - end, [], CRLDistPoints), - public_key:pkix_crls_validate(Cert, - DPointsAndCRLs, - [{issuer_fun, - {fun issuer_function/4, State}}]) - end. - -%% @doc Given a list of distribution points for CRLs, certificates and -%% both trusted and intermediary certificates, attempt to build and -%% authority chain back via build_chain to verify that it is valid. -%% -issuer_function(_DP, CRL, _Issuer, {TrustedCAs, IntermediateCerts}) -> - %% XXX the 'Issuer' we get passed here is the AuthorityKeyIdentifier, - %% which we are not currently smart enough to understand - %% Read the CA certs out of the file - ct:log("~p:~p~nissuer_function(~nCRL=~p,~nLast param=~p)",[?MODULE,?LINE,CRL, {TrustedCAs, IntermediateCerts}]), - Certs = [public_key:pkix_decode_cert(DER, otp) || DER <- TrustedCAs], - %% get the real issuer out of the CRL - Issuer = public_key:pkix_normalize_name( - pubkey_cert_records:transform( - CRL#'CertificateList'.tbsCertList#'TBSCertList'.issuer, decode)), - %% assume certificates are ordered from root to tip - case find_issuer(Issuer, IntermediateCerts ++ Certs) of - undefined -> - ct:log("~p:~p~nunable to find certificate matching CRL issuer ~p~n", - [?MODULE,?LINE, Issuer]), - error; - IssuerCert -> - ct:log("~p:~p~nIssuerCert=~p~n)",[?MODULE,?LINE,IssuerCert]), - case build_chain({public_key:pkix_encode('OTPCertificate', - IssuerCert, - otp), - IssuerCert}, IntermediateCerts, Certs, []) of - undefined -> - error; - {OTPCert, Path} -> - {ok, OTPCert, Path} - end - end. - -%% @doc Attempt to build authority chain back using intermediary -%% certificates, falling back on trusted certificates if the -%% intermediary chain of certificates does not fully extend to the -%% root. -%% -%% Returns: {RootCA :: #OTPCertificate{}, Chain :: [der_encoded()]} -%% -build_chain({DER, Cert}, IntCerts, TrustedCerts, Acc) -> - %% check if this cert is self-signed, if it is, we've reached the - %% root of the chain - Issuer = public_key:pkix_normalize_name( - Cert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer), - Subject = public_key:pkix_normalize_name( - Cert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject), - case Issuer == Subject of - true -> - case find_issuer(Issuer, TrustedCerts) of - undefined -> - ct:log("~p:~p~nself-signed certificate is NOT trusted~n",[?MODULE,?LINE]), - undefined; - TrustedCert -> - %% return the cert from the trusted list, to prevent - %% issuer spoofing - {TrustedCert, - [public_key:pkix_encode( - 'OTPCertificate', TrustedCert, otp)|Acc]} - end; - false -> - Match = lists:foldl( - fun(C, undefined) -> - S = public_key:pkix_normalize_name(C#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject), - %% compare the subject to the current issuer - case Issuer == S of - true -> - %% we've found our man - {public_key:pkix_encode('OTPCertificate', C, otp), C}; - false -> - undefined - end; - (_E, A) -> - %% already matched - A - end, undefined, IntCerts), - case Match of - undefined when IntCerts /= TrustedCerts -> - %% continue the chain by using the trusted CAs - ct:log("~p:~p~nRan out of intermediate certs, switching to trusted certs~n",[?MODULE,?LINE]), - build_chain({DER, Cert}, TrustedCerts, TrustedCerts, Acc); - undefined -> - ct:log("Can't construct chain of trust beyond ~p~n", - [?MODULE,?LINE, get_common_name(Cert)]), - %% can't find the current cert's issuer - undefined; - Match -> - build_chain(Match, IntCerts, TrustedCerts, [DER|Acc]) - end - end. - -%% @doc Given a certificate and a list of trusted or intermediary -%% certificates, attempt to find a match in the list or bail with -%% undefined. -find_issuer(Issuer, Certs) -> - lists:foldl( - fun(OTPCert, undefined) -> - %% check if this certificate matches the issuer - Normal = public_key:pkix_normalize_name( - OTPCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject), - case Normal == Issuer of - true -> - OTPCert; - false -> - undefined - end; - (_E, Acc) -> - %% already found a match - Acc - end, undefined, Certs). + ssl_test_lib:close(Client). -%% @doc Find distribution points for a given CRL and then attempt to -%% fetch the CRL from the first available. -fetch_point(#'DistributionPoint'{distributionPoint={fullName, Names}}) -> - Decoded = [{NameType, - pubkey_cert_records:transform(Name, decode)} - || {NameType, Name} <- Names], - ct:log("~p:~p~ncall fetch(~nDecoded=~p~n)",[?MODULE,?LINE,Decoded]), - fetch(Decoded). - -%% @doc Given a list of locations to retrieve a CRL from, attempt to -%% retrieve either from a file or http resource and bail as soon as -%% it can be found. -%% -%% Currently, only hand a armored PEM or DER encoded file, with -%% defaulting to DER. -%% -fetch([]) -> - not_available; -fetch([{uniformResourceIdentifier, "http"++_=URL}|Rest]) -> - ct:log("~p:~p~ngetting CRL from ~p~n", [?MODULE,?LINE, URL]), - case httpc:request(get, {URL, []}, [], [{body_format, binary}]) of - {ok, {_Status, _Headers, Body}} -> - case Body of - <<"-----BEGIN", _/binary>> -> - ct:log("~p:~p~npublic_key:pem_decode,~nBody=~p~n)",[?MODULE,?LINE,Body]), - [{'CertificateList', - DER, _}=CertList] = public_key:pem_decode(Body), - ct:log("~p:~p~npublic_key:pem_entry_decode,~nCertList=~p~n)",[?MODULE,?LINE,CertList]), - {DER, public_key:pem_entry_decode(CertList)}; - _ -> - ct:log("~p:~p~npublic_key:pem_entry_decode,~nBody=~p~n)",[?MODULE,?LINE,{'CertificateList', Body, not_encrypted}]), - %% assume DER encoded - try - public_key:pem_entry_decode({'CertificateList', Body, not_encrypted}) - of - CertList -> {Body, CertList} - catch - _C:_E -> - ct:log("~p:~p~nfailed DER assumption~nRest=~p", [?MODULE,?LINE,Rest]), - fetch(Rest) - end - end; - {error, _Reason} -> - ct:log("~p:~p~nfailed to get CRL ~p~n", [?MODULE,?LINE, _Reason]), - fetch(Rest); - Other -> - ct:log("~p:~p~nreally failed to get CRL ~p~n", [?MODULE,?LINE, Other]), - fetch(Rest) - end; -fetch([Loc|Rest]) -> - %% unsupported CRL location - ct:log("~p:~p~nunable to fetch CRL from unsupported location ~p~n", - [?MODULE,?LINE, Loc]), - fetch(Rest). +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +is_idp(idp_crl) -> + true; +is_idp(_) -> + false. + +init_certs(_,v1_crl, Config) -> + {[{v2_crls, false}], Config}; +init_certs(_, idp_crl, Config) -> + Port = ?config(httpd_port, Config), + {[{crl_port,Port}, + {issuing_distribution_point, true}], Config + }; +init_certs(_,_,Config) -> + {[], Config}. -%% get the common name attribute out of an OTPCertificate record -get_common_name(OTPCert) -> - %% You'd think there'd be an easier way than this giant mess, but I - %% couldn't find one. - {rdnSequence, Subject} = OTPCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject, - case [Attribute#'AttributeTypeAndValue'.value || [Attribute] <- Subject, - Attribute#'AttributeTypeAndValue'.type == ?'id-at-commonName'] of - [Att] -> - case Att of - {teletexString, Str} -> Str; - {printableString, Str} -> Str; - {utf8String, Bin} -> binary_to_list(Bin) - end; - _ -> - unknown - end. +make_dir_path(PathComponents) -> + lists:foldl(fun(F,P0) -> file:make_dir(P=filename:join(P0,F)), P end, + "", + PathComponents). diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 1a1b2af8d4..092015d3d8 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2007-2013. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -38,12 +39,9 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> - [{ct_hooks,[ts_install_cth]}]. - all() -> - [basic, payload, plain_options, plain_verify_options]. + [basic, payload, plain_options, plain_verify_options, nodelay_option, + listen_port_options, listen_options, use_interface]. groups() -> []. @@ -90,17 +88,15 @@ init_per_testcase(Case, Config) when is_list(Config) -> common_init(Case, Config). common_init(Case, Config) -> - Dog = ?t:timetrap(?t:seconds(?DEFAULT_TIMETRAP_SECS)), - [{watchdog, Dog},{testcase, Case}|Config]. + ct:timetrap({seconds, ?DEFAULT_TIMETRAP_SECS}), + [{testcase, Case}|Config]. end_per_testcase(Case, Config) when is_list(Config) -> Flags = proplists:get_value(old_flags, Config), catch os:putenv("ERL_FLAGS", Flags), common_end(Case, Config). -common_end(_, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), +common_end(_, _Config) -> ok. %%-------------------------------------------------------------------- @@ -255,6 +251,146 @@ plain_verify_options(Config) when is_list(Config) -> stop_ssl_node(NH1), stop_ssl_node(NH2), success(Config). +%%-------------------------------------------------------------------- +nodelay_option() -> + [{doc,"Test specifying dist_nodelay option"}]. +nodelay_option(Config) -> + try + %% The default is 'true', so try setting it to 'false'. + application:set_env(kernel, dist_nodelay, false), + basic(Config) + after + application:unset_env(kernel, dist_nodelay) + end. + +listen_port_options() -> + [{doc, "Test specifying listening ports"}]. +listen_port_options(Config) when is_list(Config) -> + %% Start a node, and get the port number it's listening on. + NH1 = start_ssl_node(Config), + Node1 = NH1#node_handle.nodename, + Name1 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name1, Port1} = lists:keyfind(Name1, 1, NodesPorts), + + %% Now start a second node, configuring it to use the same port + %% number. + PortOpt1 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ + " inet_dist_listen_max " ++ integer_to_list(Port1), + + try start_ssl_node([{additional_dist_opts, PortOpt1} | Config]) of + #node_handle{} -> + %% If the node was able to start, it didn't take the port + %% option into account. + exit(unexpected_success) + catch + exit:{accept_failed, timeout} -> + %% The node failed to start, as expected. + ok + end, + + %% Try again, now specifying a high max port. + PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ + " inet_dist_listen_max 65535", + NH2 = start_ssl_node([{additional_dist_opts, PortOpt2} | Config]), + Node2 = NH2#node_handle.nodename, + Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), + {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), + {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), + + %% The new port should be higher: + if Port2 > Port1 -> + ok; + true -> + error({port, Port2, not_higher_than, Port1}) + end, + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). +%%-------------------------------------------------------------------- +listen_options() -> + [{doc, "Test inet_dist_listen_options"}]. +listen_options(Config) when is_list(Config) -> + Prio = 1, + case gen_udp:open(0, [{priority,Prio}]) of + {ok,Socket} -> + case inet:getopts(Socket, [priority]) of + {ok,[{priority,Prio}]} -> + ok = gen_udp:close(Socket), + do_listen_options(Prio, Config); + _ -> + ok = gen_udp:close(Socket), + {skip, + "Can not set priority "++integer_to_list(Prio)++ + " on socket"} + end; + {error,_} -> + {skip, "Can not set priority on socket"} + end. + +do_listen_options(Prio, Config) -> + PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]", + PriorityString = + case os:cmd("echo [{a,1}]") of + "[{a,1}]"++_ -> + PriorityString0; + _ -> + %% Some shells need quoting of [{}] + "'"++PriorityString0++"'" + end, + + Options = "-kernel inet_dist_listen_options " ++ PriorityString, + + NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + PrioritiesNode1 = + apply_on_ssl_node(NH1, fun get_socket_priorities/0), + PrioritiesNode2 = + apply_on_ssl_node(NH2, fun get_socket_priorities/0), + + Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], + ?t:format("Elevated1: ~p~n", [Elevated1]), + Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], + ?t:format("Elevated2: ~p~n", [Elevated2]), + [_|_] = Elevated1, + [_|_] = Elevated2, + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). +%%-------------------------------------------------------------------- +use_interface() -> + [{doc, "Test inet_dist_use_interface"}]. +use_interface(Config) when is_list(Config) -> + %% Force the node to listen only on the loopback interface. + IpString = "'{127,0,0,1}'", + Options = "-kernel inet_dist_use_interface " ++ IpString, + + %% Start a node, and get the port number it's listening on. + NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), + Node1 = NH1#node_handle.nodename, + Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name, Port} = lists:keyfind(Name, 1, NodesPorts), + + %% Now find the socket listening on that port, and check its sockname. + Sockets = apply_on_ssl_node( + NH1, + fun() -> + [inet:sockname(P) || + P <- erlang:ports(), + {ok, Port} =:= (catch inet:port(P))] + end), + %% And check that it's actually listening on localhost. + [{ok,{{127,0,0,1},Port}}] = Sockets, + + stop_ssl_node(NH1), + success(Config). %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- @@ -269,6 +405,12 @@ tstsrvr_format(Fmt, ArgList) -> send_to_tstcntrl(Message) -> send_to_tstsrvr({message, Message}). +get_socket_priorities() -> + [Priority || + {ok,[{priority,Priority}]} <- + [inet:getopts(Port, [priority]) || + Port <- erlang:ports(), + element(2, erlang:port_info(Port, name)) =:= "tcp_inet"]]. %% %% test_server side api @@ -617,7 +759,7 @@ setup_certs(Config) -> ok = file:make_dir(NodeDir), ok = file:make_dir(RGenDir), make_randfile(RGenDir), - make_certs:all(RGenDir, NodeDir), + {ok, _} = make_certs:all(RGenDir, NodeDir), SDir = filename:join([NodeDir, "server"]), SC = filename:join([SDir, "cert.pem"]), SK = filename:join([SDir, "key.pem"]), diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 8dca733526..b0bb77c598 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2014. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -31,8 +32,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [decode_hello_handshake, decode_single_hello_extension_correctly, decode_supported_elliptic_curves_hello_extension_correctly, @@ -40,7 +39,47 @@ all() -> [decode_hello_handshake, encode_single_hello_sni_extension_correctly, decode_single_hello_sni_extension_correctly, decode_empty_server_sni_correctly, - select_proper_tls_1_2_rsa_default_hashsign]. + select_proper_tls_1_2_rsa_default_hashsign, + ignore_hassign_extension_pre_tls_1_2]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. +end_per_suite(Config) -> + Config. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_,Config) -> + Config. + +init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case is_supported(sha512) of + true -> + ssl:start(), + %% make rsa certs using oppenssl + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), + Config = ssl_test_lib:cert_options(Config0), + ct:timetrap({seconds, 5}), + Config; + false -> + {skip, "Crypto did not support sha512"} + end + catch _:_ -> + {skip, "Crypto did not start"} + end; +init_per_testcase(_, Config0) -> + Config0. + +end_per_testcase(ignore_hassign_extension_pre_tls_1_2, _) -> + crypto:stop(); +end_per_testcase(_TestCase, Config) -> + Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- @@ -121,3 +160,18 @@ select_proper_tls_1_2_rsa_default_hashsign(_Config) -> {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,2}), {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,0}). + +ignore_hassign_extension_pre_tls_1_2(Config) -> + Opts = ?config(server_opts, Config), + CertFile = proplists:get_value(certfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile), + HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}]}, + {sha512, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,3}), + %%% Ignore + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,2}), + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,0}). + +is_supported(Hash) -> + Algos = crypto:supports(), + Hashs = proplists:get_value(hashs, Algos), + lists:member(Hash, Hashs). diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index 30c0a67a36..6b71fe6d28 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2013. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,8 +30,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, @@ -70,10 +69,8 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config), - ?config(priv_dir, Config))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -102,6 +99,15 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -172,7 +178,7 @@ no_client_negotiate_but_server_supports_npn(Config) when is_list(Config) -> run_npn_handshake(Config, [], [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}], - {error, next_protocol_not_negotiated}). + {error, protocol_not_negotiated}). %-------------------------------------------------------------------------------- @@ -180,7 +186,7 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) -> run_npn_handshake(Config, [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], - {error, next_protocol_not_negotiated}). + {error, protocol_not_negotiated}). %-------------------------------------------------------------------------------- renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> @@ -311,8 +317,8 @@ run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> assert_npn(Socket, Protocol) -> ct:log("Negotiated Protocol ~p, Expecting: ~p ~n", - [ssl:negotiated_next_protocol(Socket), Protocol]), - Protocol = ssl:negotiated_next_protocol(Socket). + [ssl:negotiated_protocol(Socket), Protocol]), + Protocol = ssl:negotiated_protocol(Socket). assert_npn_and_renegotiate_and_send_data(Socket, Protocol, Data) -> assert_npn(Socket, Protocol), @@ -332,7 +338,7 @@ ssl_receive_and_assert_npn(Socket, Protocol, Data) -> ssl_send(Socket, Data) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), ssl:send(Socket, Data). ssl_receive(Socket, Data) -> @@ -340,7 +346,7 @@ ssl_receive(Socket, Data) -> ssl_receive(Socket, Data, Buffer) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), receive {ssl, Socket, MoreData} -> ct:log("Received ~p~n",[MoreData]), @@ -360,4 +366,4 @@ ssl_receive(Socket, Data, Buffer) -> connection_info_result(Socket) -> - ssl:connection_info(Socket). + ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 68ff9172e9..fa7187b6c0 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2013. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -32,9 +33,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [encode_and_decode_npn_client_hello_test, encode_and_decode_npn_server_hello_test, @@ -43,6 +41,14 @@ all() -> create_server_hello_with_advertised_protocols_test, create_server_hello_with_no_advertised_protocols_test]. +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index d50498f547..08a66ec07a 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2013. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -37,16 +38,12 @@ -define(uint24(X), << ?UINT24(X) >> ). -define(uint32(X), << ?UINT32(X) >> ). -define(uint64(X), << ?UINT64(X) >> ). --define(TIMEOUT, 120000). -define(MANY, 1000). -define(SOME, 50). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, @@ -140,10 +137,8 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config), - ?config(priv_dir, Config))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -172,10 +167,9 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 15}), + Config. end_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index f95eae51b7..fb3890a811 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2013. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,8 +30,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, @@ -72,7 +71,7 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config)), + {ok, _} = make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -100,10 +99,27 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; + TestCase == server_echos_active_once_huge; + TestCase == server_echos_active_huge; + TestCase == client_echos_passive_huge; + TestCase == client_echos_active_once_huge; + TestCase == client_echos_active_huge -> + ct:timetrap({seconds, 90}), + Config; + +init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; + TestCase == server_echos_active_once_big; + TestCase == server_echos_active_big; + TestCase == client_echos_passive_big; + TestCase == client_echos_active_once_big; + TestCase == client_echos_active_big -> + ct:timetrap({seconds, 60}), + Config; + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 15}), + Config. end_per_testcase(_TestCase, Config) -> Config. diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 843079e2fe..3e96276258 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2015-2015. 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/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -44,11 +45,8 @@ init_per_suite(Config0) -> ok -> ssl:start(), %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), ssl_test_lib:cert_options(Config1) catch _:_ -> @@ -64,15 +62,16 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(pem_cleanup, Config) -> - ssl:stop(), +init_per_testcase(pem_cleanup = Case, Config) -> + end_per_testcase(Case, Config) , application:load(ssl), application:set_env(ssl, ssl_pem_cache_clean, ?CLEANUP_INTERVAL), ssl:start(), + ct:timetrap({minutes, 1}), Config. end_per_testcase(_TestCase, Config) -> - %%ssl:stop(), + ssl:stop(), Config. %%-------------------------------------------------------------------- @@ -113,9 +112,9 @@ get_pem_cache() -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - case element(5, State) of - [_CertDb, _FileRefDb, PemChace] -> - PemChace; + case element(6, State) of + [_CertDb, _FileRefDb, PemCache| _] -> + PemCache; _ -> undefined end. diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index c31f6c2d7d..924898f6fa 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. 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/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -41,31 +42,25 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [session_cleanup, session_cache_process_list, - session_cache_process_mnesia]. + session_cache_process_mnesia, + client_unique_session]. groups() -> []. init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), - %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - - Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config = ssl_test_lib:cert_options(Config1), - [{watchdog, Dog} | Config] + %% make rsa certs using + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), + Config = ssl_test_lib:make_dsa_cert(Config0), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -87,30 +82,31 @@ init_per_testcase(session_cache_process_mnesia, Config) -> mnesia:start(), init_customized_session_cache(mnesia, Config); -init_per_testcase(session_cleanup, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), +init_per_testcase(session_cleanup, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, 5), application:set_env(ssl, session_delay_cleanup_time, ?DELAY), ssl:start(), - [{watchdog, Dog} | Config]; + ct:timetrap({seconds, 20}), + Config; -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(client_unique_session, Config) -> + ct:timetrap({seconds, 20}), + Config. -init_customized_session_cache(Type, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), +init_customized_session_cache(Type, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_cb, ?MODULE), - application:set_env(ssl, session_cb_init_args, [Type]), + application:set_env(ssl, session_cb_init_args, [{type, Type}]), ssl:start(), - [{watchdog, Dog} | Config]. + catch (end_per_testcase(list_to_atom("session_cache_process" ++ atom_to_list(Type)), + Config)), + ets:new(ssl_test, [named_table, public, set]), + ets:insert(ssl_test, {type, Type}), + ct:timetrap({seconds, 5}), + Config. end_per_testcase(session_cache_process_list, Config) -> application:unset_env(ssl, session_cb), @@ -126,16 +122,50 @@ end_per_testcase(session_cleanup, Config) -> application:unset_env(ssl, session_delay_cleanup_time), application:unset_env(ssl, session_lifetime), end_per_testcase(default_action, Config); -end_per_testcase(_TestCase, Config) -> +end_per_testcase(Case, Config) when Case == session_cache_process_list; + Case == session_cache_process_mnesia -> + ets:delete(ssl_test), + Config; +end_per_testcase(_, Config) -> Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- +client_unique_session() -> + [{doc, "Test session table does not grow when client " + "sets up many connections"}]. +client_unique_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + LastClient = clients_start(Server, + ClientNode, Hostname, Port, ClientOpts, 20), + receive + {LastClient, {ok, _}} -> + ok + end, + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + 1 = ets:info(ClientCache, size), + + ssl_test_lib:close(Server, 500), + ssl_test_lib:close(LastClient). + session_cleanup() -> [{doc, "Test that sessions are cleand up eventually, so that the session table " "does not grow and grow ..."}]. -session_cleanup(Config)when is_list(Config) -> +session_cleanup(Config) when is_list(Config) -> process_flag(trap_exit, true), ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), @@ -149,9 +179,9 @@ session_cleanup(Config)when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, ClientOpts}]), SessionInfo = receive {Server, Info} -> @@ -164,12 +194,13 @@ session_cleanup(Config)when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Cache = element(2, State), - SessionTimer = element(6, State), + ClientCache = element(2, State), + ServerCache = element(3, State), + SessionTimer = element(7, State), Id = proplists:get_value(session_id, SessionInfo), - CSession = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), - SSession = ssl_session_cache:lookup(Cache, {Port, Id}), + CSession = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}), + SSession = ssl_session_cache:lookup(ServerCache, {Port, Id}), true = CSession =/= undefined, true = SSession =/= undefined, @@ -185,8 +216,8 @@ session_cleanup(Config)when is_list(Config) -> ct:sleep(?SLEEP), %% Make sure clean has had time to run - undefined = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), - undefined = ssl_session_cache:lookup(Cache, {Port, Id}), + undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}), + undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}), process_flag(trap_exit, false), ssl_test_lib:close(Server), @@ -208,7 +239,7 @@ get_delay_timers() -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - case element(7, State) of + case element(8, State) of {undefined, undefined} -> ct:sleep(?SLEEP), get_delay_timers(); @@ -236,16 +267,16 @@ session_cache_process_mnesia(Config) when is_list(Config) -> %%% Session cache API callbacks %%-------------------------------------------------------------------- -init([Type]) -> - ets:new(ssl_test, [named_table, public, set]), - ets:insert(ssl_test, {type, Type}), - case Type of +init(Opts) -> + case proplists:get_value(type, Opts) of list -> spawn(fun() -> session_loop([]) end); mnesia -> mnesia:start(), - {atomic,ok} = mnesia:create_table(sess_cache, []), - sess_cache + Name = atom_to_list(proplists:get_value(role, Opts)), + TabName = list_to_atom(Name ++ "sess_cache"), + {atomic,ok} = mnesia:create_table(TabName, []), + TabName end. session_cb() -> @@ -258,7 +289,7 @@ terminate(Cache) -> Cache ! terminate; mnesia -> catch {atomic,ok} = - mnesia:delete_table(sess_cache) + mnesia:delete_table(Cache) end. lookup(Cache, Key) -> @@ -268,10 +299,10 @@ lookup(Cache, Key) -> receive {Cache, Res} -> Res end; mnesia -> case mnesia:transaction(fun() -> - mnesia:read(sess_cache, + mnesia:read(Cache, Key, read) end) of - {atomic, [{sess_cache, Key, Value}]} -> + {atomic, [{Cache, Key, Value}]} -> Value; _ -> undefined @@ -285,8 +316,8 @@ update(Cache, Key, Value) -> mnesia -> {atomic, ok} = mnesia:transaction(fun() -> - mnesia:write(sess_cache, - {sess_cache, Key, Value}, write) + mnesia:write(Cache, + {Cache, Key, Value}, write) end) end. @@ -297,7 +328,7 @@ delete(Cache, Key) -> mnesia -> {atomic, ok} = mnesia:transaction(fun() -> - mnesia:delete(sess_cache, Key) + mnesia:delete(Cache, Key) end) end. @@ -308,7 +339,7 @@ foldl(Fun, Acc, Cache) -> receive {Cache, Res} -> Res end; mnesia -> Foldl = fun() -> - mnesia:foldl(Fun, Acc, sess_cache) + mnesia:foldl(Fun, Acc, Cache) end, {atomic, Res} = mnesia:transaction(Foldl), Res @@ -325,8 +356,8 @@ select_session(Cache, PartialKey) -> mnesia -> Sel = fun() -> mnesia:select(Cache, - [{{sess_cache,{PartialKey,'$1'}, '$2'}, - [],['$$']}]) + [{{Cache,{PartialKey,'_'}, '$1'}, + [],['$1']}]) end, {atomic, Res} = mnesia:transaction(Sel), Res @@ -354,8 +385,8 @@ session_loop(Sess) -> Pid ! {self(), Res}, session_loop(Sess); {Pid,select_session,PKey} -> - Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 -> - [[Id, Session]|Acc]; + Sel = fun({{PKey0, _Id},Session}, Acc) when PKey == PKey0 -> + [Session | Acc]; (_,Acc) -> Acc end, @@ -370,3 +401,23 @@ session_loop(Sess) -> session_cache_process(_Type,Config) when is_list(Config) -> ssl_basic_SUITE:reuse_session(Config). + + +clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) -> + %% Make sure session is registered + ct:sleep(?SLEEP * 2), + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {?MODULE, connection_info_result, []}}, + {from, self()}, {options, ClientOpts}]); +clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) -> + spawn_link(ssl_test_lib, start_client, + [[{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]]), + Server ! listen, + clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1). + +connection_info_result(Socket) -> + ssl:connection_information(Socket, [protocol, cipher_suite]). diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl new file mode 100644 index 0000000000..f6ffe91027 --- /dev/null +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -0,0 +1,186 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ssl_sni_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> [no_sni_header, + sni_match, + sni_no_match, + no_sni_header_fun, + sni_match_fun, + sni_no_match_fun]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl:start(), + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), + ssl_test_lib:cert_options(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_) -> + ssl:stop(), + application:stop(crypto). + +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +no_sni_header(Config) -> + run_handshake(Config, undefined, undefined, "server"). + +no_sni_header_fun(Config) -> + run_sni_fun_handshake(Config, undefined, undefined, "server"). + +sni_match(Config) -> + run_handshake(Config, "a.server", "a.server", "a.server"). + +sni_match_fun(Config) -> + run_sni_fun_handshake(Config, "a.server", "a.server", "a.server"). + +sni_no_match(Config) -> + run_handshake(Config, "c.server", undefined, "server"). + +sni_no_match_fun(Config) -> + run_sni_fun_handshake(Config, "c.server", undefined, "server"). + + +%%-------------------------------------------------------------------- +%% Internal Functions ------------------------------------------------ +%%-------------------------------------------------------------------- +ssl_recv(SSLSocket, Expect) -> + ssl_recv(SSLSocket, "", Expect). + +ssl_recv(SSLSocket, CurrentData, ExpectedData) -> + receive + {ssl, SSLSocket, Data} -> + NeweData = CurrentData ++ Data, + case NeweData of + ExpectedData -> + ok; + _ -> + ssl_recv(SSLSocket, NeweData, ExpectedData) + end; + Other -> + ct:fail({unexpected_message, Other}) + after 4000 -> + ct:fail({timeout, CurrentData, ExpectedData}) + end. + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + {ok, [{sni_hostname, Hostname}]} = ssl:connection_information(SSLSocket, [sni_hostname]), + Hostname. + +rdnPart([[#'AttributeTypeAndValue'{type=Type, value=Value} | _] | _], Type) -> + Value; +rdnPart([_ | Tail], Type) -> + rdnPart(Tail, Type); +rdnPart([], _) -> + unknown. + +rdn_to_string({utf8String, Binary}) -> + erlang:binary_to_list(Binary); +rdn_to_string({printableString, String}) -> + String. + +recv_and_certificate(SSLSocket) -> + ssl_recv(SSLSocket, "OK"), + {ok, PeerCert} = ssl:peercert(SSLSocket), + #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = {rdnSequence, Subject}}} + = public_key:pkix_decode_cert(PeerCert, otp), + ct:log("Subject of certificate received from server: ~p", [Subject]), + rdn_to_string(rdnPart(Subject, ?'id-at-commonName')). + +run_sni_fun_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, " + "ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = ?config(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = ?config(server_opts, Config) ++ [{sni_fun, SNIFun}], + ClientOptions = + case SNIHostname of + undefined -> + ?config(client_opts, Config); + _ -> + [{server_name_indication, SNIHostname}] ++ ?config(client_opts, Config) + end, + ct:log("Options: ~p", [[ServerOptions, ClientOptions]]), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, recv_and_certificate, []}}, + {options, ClientOptions}]), + ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +run_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, " + "ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = ?config(sni_server_opts, Config) ++ ?config(server_opts, Config), + ClientOptions = + case SNIHostname of + undefined -> + ?config(client_opts, Config); + _ -> + [{server_name_indication, SNIHostname}] ++ ?config(client_opts, Config) + end, + ct:log("Options: ~p", [[ServerOptions, ClientOptions]]), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, recv_and_certificate, []}}, + {options, ClientOptions}]), + ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 74d71263de..f25f6f9425 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2014. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -187,6 +188,7 @@ run_client(Opts) -> Transport = proplists:get_value(transport, Opts, ssl), Options = proplists:get_value(options, Opts), ct:log("~p:~p~n~p:connect(~p, ~p)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Node]), + ct:log("SSLOpts: ~p", [Options]), case rpc:call(Node, Transport, connect, [Host, Port, Options]) of {ok, Socket} -> Pid ! {connected, Socket}, @@ -224,6 +226,17 @@ run_client(Opts) -> ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), Pid ! {self(), {error, Reason}} end; + {error, econnreset = Reason} -> + case get(retries) of + N when N < 5 -> + ct:log("~p:~p~neconnreset retries=~p sleep ~p",[?MODULE,?LINE, N,?SLEEP]), + put(retries, N+1), + ct:sleep(?SLEEP), + run_client(Opts); + _ -> + ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), + Pid ! {self(), {error, Reason}} + end; {error, Reason} -> ct:log("~p:~p~nClient: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), Pid ! {connect_failed, Reason}; @@ -239,7 +252,21 @@ close(Pid) -> receive {'DOWN', Monitor, process, Pid, Reason} -> erlang:demonitor(Monitor), - ct:log("~p:~p~nPid: ~p down due to:~p ~n", [?MODULE,?LINE, Pid, Reason]) + ct:log("~p:~p~nPid: ~p down due to:~p ~n", [?MODULE,?LINE, Pid, Reason]) + + end. + +close(Pid, Timeout) -> + ct:log("~p:~p~n Close ~p ~n", [?MODULE,?LINE, Pid]), + Monitor = erlang:monitor(process, Pid), + Pid ! close, + receive + {'DOWN', Monitor, process, Pid, Reason} -> + erlang:demonitor(Monitor), + ct:log("~p:~p~nPid: ~p down due to:~p ~n", [?MODULE,?LINE, Pid, Reason]) + after + Timeout -> + exit(Pid, kill) end. check_result(Server, ServerMsg, Client, ClientMsg) -> @@ -253,7 +280,6 @@ check_result(Server, ServerMsg, Client, ClientMsg) -> {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Server, ServerMsg, Client, ClientMsg); - Unexpected -> Reason = {{expected, {Client, ClientMsg}}, {expected, {Server, ServerMsg}}, {got, Unexpected}}, @@ -267,6 +293,9 @@ check_result(Pid, Msg) -> {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Pid,Msg); + %% {Port, {exit_status, Status}} when is_port(Port) -> + %% ct:log("~p:~p Exit status: ~p~n",[?MODULE,?LINE, Status]), + %% check_result(Pid, Msg); Unexpected -> Reason = {{expected, {Pid, Msg}}, {got, Unexpected}}, @@ -351,7 +380,12 @@ cert_options(Config) -> BadKeyFile = filename:join([?config(priv_dir, Config), "badkey.pem"]), PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, - [{client_opts, [{ssl_imp, new},{reuseaddr, true}]}, + + SNIServerACertFile = filename:join([?config(priv_dir, Config), "a.server", "cert.pem"]), + SNIServerAKeyFile = filename:join([?config(priv_dir, Config), "a.server", "key.pem"]), + SNIServerBCertFile = filename:join([?config(priv_dir, Config), "b.server", "cert.pem"]), + SNIServerBKeyFile = filename:join([?config(priv_dir, Config), "b.server", "key.pem"]), + [{client_opts, []}, {client_verification_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, @@ -411,7 +445,17 @@ cert_options(Config) -> {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, {certfile, BadCertFile}, {keyfile, ServerKeyFile}]}, {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, - {certfile, ServerCertFile}, {keyfile, BadKeyFile}]} + {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}, + {sni_server_opts, [{sni_hosts, [ + {"a.server", [ + {certfile, SNIServerACertFile}, + {keyfile, SNIServerAKeyFile} + ]}, + {"b.server", [ + {certfile, SNIServerBCertFile}, + {keyfile, SNIServerBKeyFile} + ]} + ]}]} | Config]. @@ -760,7 +804,12 @@ send_selected_port(_,_,_) -> rsa_suites(CounterPart) -> ECC = is_sane_ecc(CounterPart), - lists:filter(fun({rsa, _, _}) -> + FIPS = is_fips(CounterPart), + lists:filter(fun({rsa, des_cbc, sha}) when FIPS == true -> + false; + ({dhe_rsa, des_cbc, sha}) when FIPS == true -> + false; + ({rsa, _, _}) -> true; ({dhe_rsa, _, _}) -> true; @@ -811,48 +860,34 @@ openssl_rsa_suites(CounterPart) -> false -> "DSS | ECDHE | ECDH" end, - lists:filter(fun(Str) -> - case re:run(Str, Names,[]) of - nomatch -> - false; - _ -> - true - end - end, Ciphers). + lists:filter(fun(Str) -> string_regex_filter(Str, Names) + end, Ciphers). openssl_dsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"DSS",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "DSS") end, Ciphers). openssl_ecdsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"ECDHE-ECDSA",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "ECDHE-ECDSA") end, Ciphers). openssl_ecdh_rsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"ECDH-RSA",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "ECDH-RSA") end, Ciphers). +string_regex_filter(Str, Search) when is_list(Str) -> + case re:run(Str, Search, []) of + nomatch -> + false; + _ -> + true + end; +string_regex_filter(_Str, _Search) -> + false. + anonymous_suites() -> Suites = [{dh_anon, rc4_128, md5}, @@ -860,6 +895,8 @@ anonymous_suites() -> {dh_anon, '3des_ede_cbc', sha}, {dh_anon, aes_128_cbc, sha}, {dh_anon, aes_256_cbc, sha}, + {dh_anon, aes_128_gcm, null}, + {dh_anon, aes_256_gcm, null}, {ecdh_anon,rc4_128,sha}, {ecdh_anon,'3des_ede_cbc',sha}, {ecdh_anon,aes_128_cbc,sha}, @@ -885,8 +922,13 @@ psk_suites() -> {rsa_psk, aes_128_cbc, sha}, {rsa_psk, aes_256_cbc, sha}, {rsa_psk, aes_128_cbc, sha256}, - {rsa_psk, aes_256_cbc, sha384} -], + {rsa_psk, aes_256_cbc, sha384}, + {psk, aes_128_gcm, null}, + {psk, aes_256_gcm, null}, + {dhe_psk, aes_128_gcm, null}, + {dhe_psk, aes_256_gcm, null}, + {rsa_psk, aes_128_gcm, null}, + {rsa_psk, aes_256_gcm, null}], ssl_cipher:filter_suites(Suites). psk_anon_suites() -> @@ -925,6 +967,10 @@ srp_dss_suites() -> {srp_dss, aes_256_cbc, sha}], ssl_cipher:filter_suites(Suites). +rc4_suites(Version) -> + Suites = ssl_cipher:rc4_suites(Version), + ssl_cipher:filter_suites(Suites). + pem_to_der(File) -> {ok, PemBin} = file:read_file(File), public_key:pem_decode(PemBin). @@ -934,7 +980,8 @@ der_to_pem(File, Entries) -> file:write_file(File, PemBin). cipher_result(Socket, Result) -> - Result = ssl:connection_info(Socket), + {ok, Info} = ssl:connection_information(Socket), + Result = {ok, {proplists:get_value(protocol, Info), proplists:get_value(cipher_suite, Info)}}, ct:log("~p:~p~nSuccessfull connect: ~p~n", [?MODULE,?LINE, Result]), %% Importante to send two packets here %% to properly test "cipher state" handling @@ -1054,6 +1101,9 @@ is_sane_ecc(openssl) -> "OpenSSL 1.0.0" ++ _ -> % Known bug in openssl %% manifests as SSL_CHECK_SERVERHELLO_TLSEXT:tls invalid ecpointformat list false; + "OpenSSL 1.0.1l" ++ _ -> + %% Breaks signature verification + false; "OpenSSL 0.9.8" ++ _ -> % Does not support ECC false; "OpenSSL 0.9.7" ++ _ -> % Does not support ECC @@ -1074,6 +1124,25 @@ is_sane_ecc(crypto) -> is_sane_ecc(_) -> true. +is_fips(openssl) -> + VersionStr = os:cmd("openssl version"), + case re:split(VersionStr, "fips") of + [_] -> + false; + _ -> + true + end; +is_fips(crypto) -> + [{_,_, Bin}] = crypto:info_lib(), + case re:split(Bin, <<"fips">>) of + [_] -> + false; + _ -> + true + end; +is_fips(_) -> + false. + cipher_restriction(Config0) -> case is_sane_ecc(openssl) of false -> @@ -1090,6 +1159,8 @@ cipher_restriction(Config0) -> check_sane_openssl_version(Version) -> case {Version, os:cmd("openssl version")} of + {_, "OpenSSL 1.0.2" ++ _} -> + true; {_, "OpenSSL 1.0.1" ++ _} -> true; {'tlsv1.2', "OpenSSL 1.0" ++ _} -> @@ -1107,15 +1178,17 @@ check_sane_openssl_version(Version) -> enough_openssl_crl_support("OpenSSL 0." ++ _) -> false; enough_openssl_crl_support(_) -> true. -wait_for_openssl_server() -> - receive - {Port, {data, Debug}} when is_port(Port) -> - ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), - %% openssl has started make sure - %% it will be in accept. Parsing - %% output is too error prone. (Even - %% more so than sleep!) - ct:sleep(?SLEEP) +wait_for_openssl_server(Port) -> + wait_for_openssl_server(Port, 10). +wait_for_openssl_server(_, 0) -> + exit(failed_to_connect_to_openssl); +wait_for_openssl_server(Port, N) -> + case gen_tcp:connect("localhost", Port, []) of + {ok, S} -> + gen_tcp:close(S); + _ -> + ct:sleep(?SLEEP), + wait_for_openssl_server(Port, N-1) end. version_flag(tlsv1) -> @@ -1130,9 +1203,10 @@ version_flag(sslv3) -> filter_suites(Ciphers0) -> Version = tls_record:highest_protocol_version([]), Supported0 = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() + ++ ssl_cipher:anonymous_suites(Version) ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), + ++ ssl_cipher:srp_suites() + ++ ssl_cipher:rc4_suites(Version), Supported1 = ssl_cipher:filter_suites(Supported0), Supported2 = [ssl:suite_definition(S) || S <- Supported1], [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)]. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 942c446ec4..16b6cb10b9 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2008-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -25,8 +26,6 @@ -include_lib("common_test/include/ct.hrl"). --define(TIMEOUT, 120000). --define(LONG_TIMEOUT, 600000). -define(SLEEP, 1000). -define(OPENSSL_RENEGOTIATE, "R\n"). -define(OPENSSL_QUIT, "Q\n"). @@ -37,8 +36,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, @@ -50,9 +47,9 @@ all() -> groups() -> [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests() ++ npn_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ npn_tests()}, - {'tlsv1', [], all_versions_tests()++ npn_tests()}, + {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, {'sslv3', [], all_versions_tests()}]. basic_tests() -> @@ -79,6 +76,18 @@ all_versions_tests() -> expired_session, ssl2_erlang_server_openssl_client]. +alpn_tests() -> + [erlang_client_alpn_openssl_server_alpn, + erlang_server_alpn_openssl_client_alpn, + erlang_client_alpn_openssl_server, + erlang_client_openssl_server_alpn, + erlang_server_alpn_openssl_client, + erlang_server_openssl_client_alpn, + erlang_client_alpn_openssl_server_alpn_renegotiate, + erlang_server_alpn_openssl_client_alpn_renegotiate, + erlang_client_alpn_npn_openssl_server_alpn_npn, + erlang_server_alpn_npn_openssl_client_alpn_npn]. + npn_tests() -> [erlang_client_openssl_server_npn, erlang_server_openssl_client_npn, @@ -89,9 +98,16 @@ npn_tests() -> erlang_client_openssl_server_npn_only_client, erlang_client_openssl_server_npn_only_server]. +sni_server_tests() -> + [erlang_server_openssl_client_sni_match, + erlang_server_openssl_client_sni_match_fun, + erlang_server_openssl_client_sni_no_match, + erlang_server_openssl_client_sni_no_match_fun, + erlang_server_openssl_client_sni_no_header, + erlang_server_openssl_client_sni_no_header_fun]. + init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), case os:find_executable("openssl") of false -> {skip, "Openssl not found"}; @@ -100,13 +116,10 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config2 = ssl_test_lib:cert_options(Config1), - Config = [{watchdog, Dog} | Config2], + Config = ssl_test_lib:cert_options(Config1), ssl_test_lib:cipher_restriction(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -135,19 +148,22 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(expired_session, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?EXPIRE * 1000 * 5), +init_per_testcase(expired_session, Config) -> + ct:timetrap(?EXPIRE * 1000 * 5), ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, ?EXPIRE), ssl:start(), - [{watchdog, Dog} | Config]; + Config; -init_per_testcase(TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - special_init(TestCase, [{watchdog, Dog} | Config]). +init_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs; + TestCase == ciphers_dsa_signed_certs -> + ct:timetrap({seconds, 45}), + special_init(TestCase, Config); + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_renegotiate; @@ -161,6 +177,36 @@ special_init(ssl2_erlang_server_openssl_client, Config) -> check_sane_openssl_sslv2(Config); special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn; + TestCase == erlang_server_alpn_openssl_client_alpn; + TestCase == erlang_client_alpn_openssl_server; + TestCase == erlang_client_openssl_server_alpn; + TestCase == erlang_server_alpn_openssl_client; + TestCase == erlang_server_openssl_client_alpn -> + check_openssl_alpn_support(Config); + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; + TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + case check_sane_openssl_renegotaite(Config, Version) of + {skip, _} = Skip -> + Skip; + _ -> + check_openssl_alpn_support(Config) + end; + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; + TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> + case check_openssl_alpn_support(Config) of + {skip, _} = Skip -> + Skip; + _ -> + check_openssl_npn_support(Config) + end; + +special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_npn; TestCase == erlang_server_openssl_client_npn; TestCase == erlang_server_openssl_client_npn_only_server; @@ -179,6 +225,16 @@ special_init(TestCase, Config) _ -> check_openssl_npn_support(Config) end; + +special_init(TestCase, Config) + when TestCase == erlang_server_openssl_client_sni_match; + TestCase == erlang_server_openssl_client_sni_no_match; + TestCase == erlang_server_openssl_client_sni_no_header; + TestCase == erlang_server_openssl_client_sni_match_fun; + TestCase == erlang_server_openssl_client_sni_no_match_fun; + TestCase == erlang_server_openssl_client_sni_no_header_fun -> + check_openssl_sni_support(Config); + special_init(_, Config) -> Config. @@ -213,7 +269,7 @@ basic_erlang_client_openssl_server(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -285,7 +341,7 @@ erlang_client_openssl_server(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -360,7 +416,7 @@ erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -478,7 +534,7 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -527,7 +583,7 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -608,7 +664,7 @@ erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -651,7 +707,7 @@ erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -790,7 +846,7 @@ erlang_client_bad_openssl_server(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -846,7 +902,7 @@ expired_session(Config) when is_list(Config) -> OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client0 = ssl_test_lib:start_client([{node, ClientNode}, @@ -924,6 +980,128 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, false). %%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_client_alpn_openssl_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, + [{alpn_advertised_protocols, [<<"spdy/2">>]}], + "", + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, + [], + "-alpn spdy/2", + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_server_alpn_openssl_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, + [{alpn_preferred_protocols, [<<"spdy/2">>]}], + "", + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, + [], + "-alpn spdy/2", + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- erlang_client_openssl_server_npn() -> [{doc,"Test erlang client with openssl server doing npn negotiation"}]. @@ -1016,6 +1194,25 @@ erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok) end), ok. +%-------------------------------------------------------------------------- +erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server"). + +erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server"). + +erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "a.server"). + +erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "a.server"). + +erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server"). + +erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server"). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ @@ -1042,6 +1239,94 @@ run_suites(Ciphers, Version, Config, Type) -> ct:fail(cipher_suite_failed_see_test_case_log) end. +client_read_check([], _Data) -> + ok; +client_read_check([Hd | T], Data) -> + case binary:match(Data, list_to_binary(Hd)) of + nomatch -> + nomatch; + _ -> + client_read_check(T, Data) + end. +client_check_result(Port, DataExpected, DataReceived) -> + receive + {Port, {data, TheData}} -> + Data = list_to_binary(TheData), + NewData = <<DataReceived/binary, Data/binary>>, + ct:log("New Data: ~p", [NewData]), + case client_read_check(DataExpected, NewData) of + ok -> + ok; + _ -> + client_check_result(Port, DataExpected, NewData) + end + after 3000 -> + ct:fail({"Time out on opensssl Client", {expected, DataExpected}, + {got, DataReceived}}) + end. +client_check_result(Port, DataExpected) -> + client_check_result(Port, DataExpected, <<"">>). + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + {ok, [{sni_hostname, Hostname}]} = ssl:connection_information(SSLSocket, [sni_hostname]), + Hostname. + +erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = ?config(sni_server_opts, Config) ++ ?config(server_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + ClientCommand = case SNIHostname of + undefined -> + "openssl s_client -connect " ++ Hostname ++ ":" ++ integer_to_list(Port); + _ -> + "openssl s_client -connect " ++ Hostname ++ ":" ++ integer_to_list(Port) ++ " -servername " ++ SNIHostname + end, + ct:log("Options: ~p", [[ServerOptions, ClientCommand]]), + ClientPort = open_port({spawn, ClientCommand}, [stderr_to_stdout]), + + %% Client check needs to be done befor server check, + %% or server check might consume client messages + ExpectedClientOutput = ["OK", "/CN=" ++ ExpectedCN ++ "/"], + client_check_result(ClientPort, ExpectedClientOutput), + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server), + ok. + + +erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = ?config(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = ?config(server_opts, Config) ++ [{sni_fun, SNIFun}], + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + ClientCommand = case SNIHostname of + undefined -> + "openssl s_client -connect " ++ Hostname ++ ":" ++ integer_to_list(Port); + _ -> + "openssl s_client -connect " ++ Hostname ++ ":" ++ integer_to_list(Port) ++ " -servername " ++ SNIHostname + end, + ct:log("Options: ~p", [[ServerOptions, ClientCommand]]), + ClientPort = open_port({spawn, ClientCommand}, [stderr_to_stdout]), + + %% Client check needs to be done befor server check, + %% or server check might consume client messages + ExpectedClientOutput = ["OK", "/CN=" ++ ExpectedCN ++ "/"], + client_check_result(ClientPort, ExpectedClientOutput), + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server). + + cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> process_flag(trap_exit, true), ct:log("Testing CipherSuite ~p~n", [CipherSuite]), @@ -1058,7 +1343,7 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), ConnectionInfo = {ok, {Version, CipherSuite}}, @@ -1122,7 +1407,7 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -1139,6 +1424,142 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens ssl_test_lib:close(Client), process_flag(trap_exit, false). +start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + + Cmd = "openssl s_server -msg -alpn http/1.1,spdy/2 -accept " ++ integer_to_list(Port) ++ ssl_test_lib:version_flag(Version) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile, + + ct:log("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + ssl_test_lib:wait_for_openssl_server(Port), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Cmd = "openssl s_client -alpn http/1.0,spdy/2 -msg -port " ++ integer_to_list(Port) ++ ssl_test_lib:version_flag(Version) ++ + " -host localhost", + + ct:log("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + + Cmd = "openssl s_server -msg -alpn http/1.1,spdy/2 -nextprotoneg spdy/3 -accept " ++ integer_to_list(Port) ++ ssl_test_lib:version_flag(Version) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile, + + ct:log("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + ssl_test_lib:wait_for_openssl_server(Port), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, + {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Cmd = "openssl s_client -alpn http/1.1,spdy/2 -nextprotoneg spdy/3 -msg -port " ++ integer_to_list(Port) ++ ssl_test_lib:version_flag(Version) ++ + " -host localhost", + + ct:log("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), ServerOpts = ?config(server_opts, Config), @@ -1161,13 +1582,13 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - ssl_test_lib:wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {?MODULE, - erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, {options, ClientOpts}]), Callback(Client, OpensslPort), @@ -1188,7 +1609,7 @@ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callbac Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), @@ -1236,15 +1657,15 @@ start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenS process_flag(trap_exit, false). -erlang_ssl_receive_and_assert_npn(Socket, Protocol, Data) -> - {ok, Protocol} = ssl:negotiated_next_protocol(Socket), +erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> + {ok, Protocol} = ssl:negotiated_protocol(Socket), erlang_ssl_receive(Socket, Data), - {ok, Protocol} = ssl:negotiated_next_protocol(Socket), + {ok, Protocol} = ssl:negotiated_protocol(Socket), ok. erlang_ssl_receive(Socket, Data) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), receive {ssl, Socket, Data} -> io:format("Received ~p~n",[Data]), @@ -1263,16 +1684,16 @@ erlang_ssl_receive(Socket, Data) -> end. connection_info(Socket, Version) -> - case ssl:connection_info(Socket) of - {ok, {Version, _} = Info} -> + case ssl:connection_information(Socket, [version]) of + {ok, [{version, Version}] = Info} -> ct:log("Connection info: ~p~n", [Info]), ok; - {ok, {OtherVersion, _}} -> + {ok, [{version, OtherVersion}]} -> {wrong_version, OtherVersion} end. connection_info_result(Socket) -> - ssl:connection_info(Socket). + ssl:connection_information(Socket). delayed_send(Socket, [ErlData, OpenSslData]) -> @@ -1287,6 +1708,14 @@ server_sent_garbage(Socket) -> end. +check_openssl_sni_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "-servername") of + 0 -> + {skip, "Current openssl doesn't support SNI"}; + _ -> + Config + end. check_openssl_npn_support(Config) -> HelpText = os:cmd("openssl s_client --help"), @@ -1297,6 +1726,15 @@ check_openssl_npn_support(Config) -> Config end. +check_openssl_alpn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "alpn") of + 0 -> + {skip, "Openssl not compiled with alpn support"}; + _ -> + Config + end. + check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; Version == 'tlsv1.2' -> case os:cmd("openssl version") of diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl new file mode 100644 index 0000000000..d65bdf6983 --- /dev/null +++ b/lib/ssl/test/ssl_upgrade_SUITE.erl @@ -0,0 +1,278 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ssl_upgrade_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-record(state, { + config, + server, + client, + soft, + result_proxy + }). + +all() -> + [ + minor_upgrade, + major_upgrade + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try {crypto:start(), erlang:system_info({wordsize, internal}) == erlang:system_info({wordsize, external})} of + {ok, true} -> + case ct_release_test:init(Config0) of + {skip, Reason} -> + {skip, Reason}; + Config -> + {ok, _} = make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config)), + ssl_test_lib:cert_options(Config) + end; + {ok, false} -> + {skip, "Test server will not handle halfwordemulator correctly. Skip as halfwordemulator is deprecated"} + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(Config) -> + ct_release_test:cleanup(Config), + crypto:stop(). + +init_per_testcase(_TestCase, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({minutes, 1}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +major_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssl, major,{?MODULE, #state{config = Config}}, Config). + +minor_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssl, minor,{?MODULE, #state{config = Config}}, Config). + +upgrade_init(CTData, #state{config = Config} = State) -> + {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssl), + ct:pal("Up: ~p", [Up]), + Soft = is_soft(Up), %% It is symmetrical, if upgrade is soft so is downgrade + Pid = spawn(?MODULE, result_proxy_init, [[]]), + case Soft of + true -> + {Server, Client} = soft_start_connection(Config, Pid), + State#state{server = Server, client = Client, + soft = Soft, + result_proxy = Pid}; + false -> + State#state{soft = Soft, result_proxy = Pid} + end. + +upgrade_upgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> + ct:pal("Restart upgrade ~n", []), + {Server, Client} = restart_start_connection(Config, Pid), + Result = check_result(Pid, Server, Client), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok = Result, + State; + +upgrade_upgraded(_, #state{server = Server0, client = Client0, + config = Config, soft = true, + result_proxy = Pid} = State) -> + ct:pal("Soft upgrade: ~n", []), + Server0 ! changed_version, + Client0 ! changed_version, + Result = check_result(Pid, Server0, Client0), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ok = Result, + {Server, Client} = soft_start_connection(Config, Pid), + State#state{server = Server, client = Client}. + +upgrade_downgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> + ct:pal("Restart downgrade: ~n", []), + {Server, Client} = restart_start_connection(Config, Pid), + Result = check_result(Pid, Server, Client), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + Pid ! stop, + ok = Result, + State; + +upgrade_downgraded(_, #state{server = Server, client = Client, soft = true, result_proxy = Pid} = State) -> + ct:pal("Soft downgrade: ~n", []), + Server ! changed_version, + Client ! changed_version, + Result = check_result(Pid, Server, Client), + Pid ! stop, + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok = Result, + State. + +use_connection(Socket) -> + ssl_test_lib:send_recv_result_active(Socket), + receive + changed_version -> + ssl_test_lib:send_recv_result_active(Socket) + end. + +soft_start_connection(Config, ResulProxy) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, ResulProxy}, + {mfa, {?MODULE, use_connection, []}}, + {options, ServerOpts}]), + + Port = inet_port(ResulProxy, Server), + Client = start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, ResulProxy}, + {mfa, {?MODULE, use_connection, []}}, + {options, ClientOpts}]), + {Server, Client}. + +restart_start_connection(Config, ResulProxy) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, ResulProxy}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = inet_port(ResulProxy, Server), + Client = start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, ResulProxy}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + {Server, Client}. + +is_soft([{restart_application, ssl}]) -> + false; +is_soft(_) -> + true. + +result_proxy_init(Args) -> + result_proxy_loop(Args). + +result_proxy_loop(Args) -> + receive + {Pid, {check_result, Server, Client}} -> + Result = do_check_result(Server, ok, Client, ok), + Pid ! {self(), Result}, + result_proxy_loop(Args); + {Pid, port, Server} -> + Port = recv_port(Server), + Pid ! Port, + result_proxy_loop(Args); + {Pid, listen} -> + recv_listen(), + Pid ! ok, + result_proxy_loop(Args); + {Pid, connected} -> + Connected = recv_connected(), + Pid ! Connected, + result_proxy_loop(Args) + end. + +check_result(Pid, Server, Client) -> + Pid ! {self(), {check_result, Server, Client}}, + receive + {Pid, Result} -> + Result + end. + +do_check_result(Server, ServerMsg, Client, ClientMsg) -> + receive + {Server, ServerMsg} -> + do_check_result(Client, ClientMsg); + + {Client, ClientMsg} -> + do_check_result(Server, ServerMsg); + Unexpected -> + {{expected, {Client, ClientMsg}}, + {expected, {Server, ServerMsg}}, {got, Unexpected}} + end. + +do_check_result(Pid, Msg) -> + receive + {Pid, Msg} -> + ok; + Unexpected -> + {{expected, {Pid, Msg}}, + {got, Unexpected}} + end. + +inet_port(Pid, Server) -> + Pid ! {self(), port, Server}, + receive + {port, Port} -> + Port + end. + +recv_port(Server) -> + receive + {Server, {port, Port}} -> + {port, Port} + end. + +recv_connected() -> + receive + {connected, _Socket} -> + ok; + {connect_failed, Reason} -> + {connect_failed, Reason} + end. + + +start_server(Args) -> + Pid = proplists:get_value(from, Args), + Result = spawn_link(ssl_test_lib, run_server, [Args]), + Pid ! {self(), listen}, + receive + ok -> + ok + end, + Result. + +start_client(Args) -> + Pid = proplists:get_value(from, Args), + Result = spawn_link(ssl_test_lib, run_client_init, [lists:delete(return_socket, Args)]), + Pid ! {self(), connected}, + receive + ok -> + Result; + Reason -> + exit(Reason) + end. + +recv_listen()-> + receive + {listen, up} -> + ok + end. |