%%
%% %CopyrightBegin%
%%
%% 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/.2
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%

-module(ssl_ECC_SUITE).

%% Note: This directive should only be used in test suites.
-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

suite() -> [{ct_hooks,[ts_install_cth]}].

all() ->
    [
     {group, 'tlsv1.2'},
     {group, 'tlsv1.1'},
     {group, 'tlsv1'}
    ].

groups() ->
    [
     {'tlsv1.2', [], all_versions_groups()},
     {'tlsv1.1', [], all_versions_groups()},
     {'tlsv1', [], all_versions_groups()},
     {'erlang_server', [], key_cert_combinations()},
     {'erlang_client', [], key_cert_combinations()},
     {'erlang', [], key_cert_combinations()}
    ].

all_versions_groups ()->
    [{group, 'erlang_server'},
     {group, 'erlang_client'},
     {group, 'erlang'}
    ].

key_cert_combinations() ->
    [client_ec_server_ec,
     client_rsa_server_ec,
     client_ec_server_rsa,
     client_rsa_server_rsa].

%%--------------------------------------------------------------------
init_per_suite(Config) ->
    catch crypto:stop(),
    try crypto:start() of
	ok ->
	    ssl:start(),
	    Config
    catch _:_ ->
	    {skip, "Crypto did not start"}
    end.

end_per_suite(_Config) ->
    ssl:stop(),
    application:stop(crypto).

%%--------------------------------------------------------------------
init_per_group(erlang_client, Config) ->
    case ssl_test_lib:is_sane_ecc(openssl) of
	true ->
	    common_init_per_group(erlang_client, [{server_type, openssl},
						  {client_type, erlang} | Config]);
	false ->
	    {skip, "Known ECC bug in openssl"}
    end;

init_per_group(erlang_server, Config) ->
    case ssl_test_lib:is_sane_ecc(openssl) of 
	true ->
	    common_init_per_group(erlang_client, [{server_type, erlang},
						  {client_type, openssl} | Config]);
	false ->
	    {skip, "Known ECC bug in openssl"}
    end;
	
init_per_group(erlang = Group, Config) ->
     case ssl_test_lib:sufficient_crypto_support(Group) of
	 true ->
	     common_init_per_group(erlang, [{server_type, erlang},
					    {client_type, erlang} | Config]);
	 false ->
	      {skip, "Crypto does not support ECC"}
     end;		 
init_per_group(Group, Config) ->
    common_init_per_group(Group, Config).

common_init_per_group(GroupName, Config) ->
    case ssl_test_lib:is_tls_version(GroupName) of
	true ->
	    ssl_test_lib:init_tls_version(GroupName),
	    [{tls_version, GroupName} | Config];
	_ ->
	   openssl_check(GroupName, 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:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
    Config.

end_per_testcase(_TestCase, Config) ->
    Config.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------

client_ec_server_ec(Config) when is_list(Config) ->
    basic_test("ec1.crt", "ec1.key", "ec2.crt", "ec2.key", Config).

client_ec_server_rsa(Config)  when is_list(Config) ->
    basic_test("ec1.crt", "ec1.key", "rsa1.crt", "rsa1.key", Config).

client_rsa_server_ec(Config)  when is_list(Config) ->
    basic_test("rsa1.crt", "rsa1.key", "ec2.crt", "ec2.key", Config).

client_rsa_server_rsa(Config)  when is_list(Config) ->
    basic_test("rsa1.crt", "rsa1.key", "rsa2.crt", "rsa2.key", Config).

%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
basic_test(ClientCert, ClientKey, ServerCert, ServerKey, Config) ->
    DataDir = ?config(data_dir, Config),
    SType = ?config(server_type, Config),
    CType = ?config(client_type, Config),
    {Server, Port} = start_server(SType,
				  filename:join(DataDir, "CA.pem"),
				  filename:join(DataDir, ServerCert),
				  filename:join(DataDir, ServerKey),
				  Config),
    Client = start_client(CType, Port, filename:join(DataDir, "CA.pem"),
			  filename:join(DataDir, ClientCert),
			  filename:join(DataDir, ClientKey), Config),
    check_result(Server, SType, Client, CType).

start_client(openssl, Port, CA, Cert, Key, _) ->
    Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
    Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++  ssl_test_lib:version_flag(Version) ++
	" -cert " ++ Cert ++ " -CAfile " ++ CA
	++ " -key " ++ Key ++ " -host localhost -msg",
    OpenSslPort =  open_port({spawn, Cmd}, [stderr_to_stdout]),
    true = port_command(OpenSslPort, "Hello world"),
    OpenSslPort;
start_client(erlang, Port, CA, Cert, Key, Config) ->
    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
			       {host, Hostname},
			       {from, self()},
			       {mfa, {ssl_test_lib, send_recv_result_active, []}},
			       {options, [{verify, verify_peer}, {cacertfile, CA},
					  {certfile, Cert}, {keyfile, Key}]}]).

start_server(openssl, CA, Cert, Key, _) ->
    Port = ssl_test_lib:inet_port(node()),
    Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
    Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ ssl_test_lib:version_flag(Version) ++
	" -cert " ++ Cert ++ " -CAfile " ++ CA
	++ " -key " ++ Key ++ " -Verify 2 -msg",
    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()},
			       {mfa, {ssl_test_lib,
				      send_recv_result_active,
				      []}},
			       {options,
				[{verify, verify_peer}, {cacertfile, CA},
				 {certfile, Cert}, {keyfile, Key}]}]),
    {Server, ssl_test_lib:inet_port(Server)}.

check_result(Server, erlang, Client, erlang) ->
    ssl_test_lib:check_result(Server, ok, Client, ok);
check_result(Server, erlang, _, _) ->
    ssl_test_lib:check_result(Server, ok);
check_result(_, _, Client, erlang) ->
    ssl_test_lib:check_result(Client, ok);
check_result(_,openssl, _, openssl) ->
    ok.

openssl_check(erlang, Config) ->
    Config;
openssl_check(_, Config) ->
    TLSVersion = ?config(tls_version, Config),
    case ssl_test_lib:check_sane_openssl_version(TLSVersion) of
	true ->
	    ssl:start(),
	    Config;
	false ->
	    {skip, "TLS version not supported by openssl"}
    end.