%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2017-2023. 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_engine_SUITE).

-behaviour(ct_suite).

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

%% Common test
-export([all/0,
         init_per_suite/1,
         init_per_testcase/2,
         end_per_suite/1,
         end_per_testcase/2
        ]).
%% Test cases
-export([private_key/1
        ]).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
    [
     private_key
    ].

init_per_suite(Config) ->
    catch crypto:stop(),
    try crypto:start() of
	ok ->
            case crypto:info_lib() of
                [{_,_, <<"OpenSSL 1.0.1s-freebsd  1 Mar 2016">>}] ->
                    {skip, "Problem with engine on OpenSSL 1.0.1s-freebsd"};
                _ ->
                    ssl_test_lib:clean_start(),
                    case crypto:get_test_engine() of
                         {ok, EngineName} ->
                            try
                                %% The test engine has it's own fake rsa sign/verify that
                                %% you don't want to use, so exclude it from methods to load:
                                Methods = 
                                    crypto:engine_get_all_methods() -- [engine_method_rsa],
                                crypto:engine_load(<<"dynamic">>,
                                                   [{<<"SO_PATH">>, EngineName},
                                                    <<"LOAD">>],
                                                   [],
                                                   Methods)
                            of
                                {ok, Engine} ->
                                    [{engine, Engine} |Config];
                                {error, Reason} ->
                                    ?CT_PAL("Reason ~p", [Reason]),
                                    {skip, "No dynamic engine support"}
                            catch error:notsup ->
                                    {skip, "No engine support in OpenSSL"}    
                            end;
                        {error, notexist} ->
                            {skip, "Test engine not found"}
                    end
            end
    catch _:_ ->
	    {skip, "Crypto did not start"}
    end.

end_per_suite(Config) ->
    Engine = proplists:get_value(engine, Config),
    crypto:engine_unload(Engine),
    ssl:stop(),
    application:stop(crypto).


init_per_testcase(_TestCase, Config) ->
    ssl:stop(),
    ssl:start(),
    ssl_test_lib:ct_log_supported_protocol_versions(Config),
    ct:timetrap({seconds, 10}),
    Config.

end_per_testcase(_TestCase, Config) ->     
    Config.

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

private_key(Config) when is_list(Config) ->
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "client_engine"]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server_engine"]),
    Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
    #{server_config := ServerConf,
      client_config := ClientConf} = GenCertData =
        public_key:pkix_test_data(#{server_chain => 
                                        #{root => [{digest, sha256},
                                                   {key, ssl_test_lib:hardcode_rsa_key(1)}],
                                          intermediates => [[{digest, sha256},
                                                            {key, ssl_test_lib:hardcode_rsa_key(2)}]],
                                          peer => [{extensions, Ext}, {digest, sha256},
                                                   {key, ssl_test_lib:hardcode_rsa_key(3)}
                                                  ]},
                                    client_chain => 
                                      #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}, {digest, sha256}],
                                        intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
                                                             {digest, sha256}]],
                                          peer => [{key, ssl_test_lib:hardcode_rsa_key(6)},{digest, sha256}]}}),
    [{server_config, FileServerConf}, 
     {client_config, FileClientConf}] = 
        x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
    
    Engine = proplists:get_value(engine, Config),

    ClientKey = engine_key(FileClientConf),
    ServerKey = engine_key(FileServerConf),

    EngineClientConf = [{key, #{algorithm => rsa,
                                engine => Engine,
                                key_id => ClientKey}} | proplists:delete(key, ClientConf)],
    
    EngineServerConf = [{key, #{algorithm => rsa,
                                engine => Engine,
                                key_id => ServerKey}} | proplists:delete(key, ServerConf)],

    EngineFileClientConf = [{key, #{algorithm => rsa,
                                    engine => Engine,
                                    key_id => ClientKey}} | 
                            proplists:delete(keyfile, FileClientConf)],
    
    EngineFileServerConf = [{key, #{algorithm => rsa,
                                    engine => Engine,
                                    key_id => ServerKey}} | 
                            proplists:delete(keyfile, FileServerConf)],

    %% Test with engine
    test_tls_connection(EngineServerConf, EngineClientConf, Config),
    
    %% Test with engine and rsa keyexchange
    RSASuites = all_kex_rsa_suites([{tls_version, 'tlsv1.2'} | Config]),
    
    test_tls_connection([{ciphers, RSASuites}, {versions, ['tlsv1.2']} | EngineServerConf], 
                        [{ciphers, RSASuites}, {versions, ['tlsv1.2']} | EngineClientConf], Config),
    
    %% Test with engine and present file arguments
    test_tls_connection(EngineFileServerConf, EngineFileClientConf, Config),
    
    %% Test that software fallback is available
    test_tls_connection(ServerConf, [{reuse_sessions, false} |ClientConf], Config).
    
engine_key(Conf) ->
    FileStr = proplists:get_value(keyfile, Conf),
    list_to_binary(FileStr).
    

test_tls_connection(ServerConf, ClientConf, 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, [{verify, verify_peer}
                                                   | ServerConf]}]),
    Port  = ssl_test_lib:inet_port(Server),
    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
					{host, Hostname},
                                        {from, self()},
                                        {mfa, {ssl_test_lib, send_recv_result_active, []}},
                                        {options,  [{verify, verify_peer} | ClientConf]}]),
    
    ssl_test_lib:check_result(Server, ok, Client, ok),
    ssl_test_lib:close(Server),
    ssl_test_lib:close(Client).

all_kex_rsa_suites(Config) ->
    Version = proplists:get_value(tls_version, Config),
    All = ssl:cipher_suites(all, Version),
    ssl:filter_cipher_suites(All,[{key_exchange, fun(rsa) -> true;(_) -> false end}]).
