aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssl/test/ssl_test_lib.erl
blob: c4f294771a11b1576efae8b19ac6c7f508be61d2 (plain) (tree)
1
2
3
4
5

                   
  
                                                        
  










                                                                           
  





                      
                                           
                                                  


                                                           
                            
 
                                           
                     
                                  
 



                             
                                                        




                                   
                                                        

                                   
                             

                                                        








                                                                               


                     

                                                           



                       

                   


                                                 
                                                           
                                                                          
                                                         
                       
                                                


                                   









                                                                   
 





                                                  

                                          
                                                  
                                          
                                                          
                                                              
                                              
                                                                     
                                                          


                        
                                                                     

                               
           

                                           

                                                                          
                
                                                                            

                                                    
                                                                                 

                          

        
                                              
                                             

                                                                    
                                                           
                                                            

                                                                                                      





                                           
        
                               
                                                                         
                                                      

                 
                                          
                 

                                                               
                                                                

                                                                                               
                                                         


                                                                                              
                                          





                                                                                  
                                                                
                                                                                               
 
                                                         
                             
                                       

                                                                                                                        
                                                                      










                                                                                                  
                                                         
                                                                
                                                                                      
 


                                                                            
                 
                                                                                  
                  
        
                                                       
                                                         


                                                                        
                 
 

                                            

                                                                       




                       
                                       

                                                                         




                       
















































                                                                                                                
 
                               




                                                                          
                                                         

                                                


                                                                

                       



























                                                                                                                

                                 




                                                                          
                                                         

                                                

                                                                


                       







                                              
                     

                                                                                             
            




                                                     

                                    
        
 



                        




                                           
                                                           
                                                 
                                                               
                                                                                           
                                     






                                                                                       

                                                                
                       

                                                                  
                                                                        

                                                                                 
                                                                      

                                                                                   
                                                            


                                
                                                                             

                                       
                   
                        
                                                                       
                                            
                                       

                                    
                      
                


                                         
                                                                                                



                                      
                                                                                                                   

                                                   










                                                                                                                   
                          
                                                                                       
                                          

        

                                                                              
                          
                                                       






                                                                                       

                                                                               

                                                                                                                  
                                                                   




                                                                               
                                                                    











                                                                                       
             
                                                       




                                                  














                                                                                     
        
 

                                                     
            

                                            


                                                                                 

                                            
                                                  
                                                                  
                                                               
                                           



                                                                     
                                                               


                                                                          
                           

        

                                              


                      


                                                                             
                                                  
                                                                  
                                  


                                                                   


                                              
                           
        
 






                                           

                                 

                                                     

                                 



                                            

                                                        
                                             


                                 

                                                     




                                                            



                                            

                                                        





                                                               
        





                                           
 





                                           





                                                         


                                 



                                      


                                 

                                                  
                                                                 


                                                                 

        









                                                          




                                                  
                                                                 


                                    


                       
                                                                             
                                                                
                                                                           
                                                           
                                                                                              
                                                                                  
                                                                             
                                                                
                                                                           
                                                           
                                                                          
                                                   
                                                                          
                                                   
                                                                              
                                                                 
                                                                              

                                                                 
                                                                          
                                                     
                                                                        
                                                   
                                                                       
                                             
    


                                                    
                                                                 

                                                             
                                                         
                                                                                       


                                                                               
                                                                                      

                                                                           
                                                   

















                                                                            
                                                                          


               



                                                                                    


                                                                                               

                                                                                           
                                                                   





                                                                                            
                                        



                     




















                                                                                                         



                                                                                         

                                                                                             
                                                           





                                                                                                   
 

































                                                                                                     
                                                                          


                                                                                            

                                                                                         
                                                                                                

                                                                                                           
                                                           






                                                                                                   



                               




                                                                                       









                                                                   
                                                                            


                                   

                                                               



















                                                                   
 
                                     
                                                          
                                                      



                                               
                                        
                                                          
                                                      



                                              
                                       
                                                          
                                                      


                                              
                                  



                                                          
                                  
                                                          
                                                      


                                              
                            



                                                                            
                            




























                                                                          


                                                                  
                                                               
                                                     
                                           
                                                                 
                                                                                         

                                                                                                        







                                                                                                   





















                                                                              
                          

                                                                                      
               

                                                                                             



                                                                                               

                                            
                                                                                                           



                                                                                  










                                                                                           



                                                                                               






                                                                                                           

                                                                           
                       
                

                  
                                 

                                                       





                   




                                                                           

                                                                                     
               

                                                                                                



                                                                                               











                                                                                            


                  
 
                             

                                                                   



                       

                           




                                                           
 
                                                                                 
                                                          
                       
                                                
                                                                         
                                                      



                                               
                                                                                
                                                                                                      
                                                                                
                                        
                                                                                    
                                                                                                               
                                                                                        

                                                                  
                                                                
                                                                         


                            
                                                                           
                                          


                                    


                             

                                                          

                           





                                                        

                                                                
                                                           
 
                                          
 
                                                                                
                                                      

                                                              

                                                                              
                                                      
                                                                     


                        
                                                                       
                                

        
                                   

                                                                        





                                 





                                                           
                                                                                 
                                                          

                                                
                                                                         
                                                      

                           
                                                            
                                                                                  
                                                            
                    
                                                                    
                                                                                           
                                                                    


                          
                           




                                                           

                         


                                                 
                                                           
                                                                          
                                           
                                            
                                             
                                                                      
                               
                                                        
                                                                                       
                                                            


                                          
                                                                                         
                                                        

                                         


                                                        
                                                                                       
                                                   


                                          
                


                                                            



                                 

                                                        

                         


                                           
                                                          
                                                 
                                                                                     
                                                   
                 
                     






                                                            
 










                                         



                                                     
                                                               



                                                      
                                                     




                                                                         














                                                                                   























                                                                                      
 








                                                                               
                                                              
 



                                                         
                                                      
                    
                                                                        
                                               

                                                                   





                                      



                                             
                     



                                               



                                                             
                                                  

                                                    
                                                                                           
                                                      
                                                        
                    










                                                                                                      










                                                                                              
                                                       
 











                                                          




















                                                                              
                                                        


                                       












                                                                                             


                                                    
                                              

                                                            
                                         




                                                                 
                                                                                                   
                                
 
                         
                     


                 















                                                                                     



















































                                                                                       
 



                                                          


                                                                          
                                         





                                                                        
                                                                         

        







                                                                        




                            








                                                                         


                                             







                                        

                                              







                                                                             

                                            
                                                                                  


                                                
                   

                                                             
                                          
                                                       
                                                     
                                       
                                   




                                                          





                                                                                   








                                                    
 

                                   
                                

                                                          



                                                                  
                                         
                                                                    
                                             
                                                                    
                                                                
                                                                    

                                                                    
                                            
                                                                    
                                               
                                                                    
                          
                              
                     






                                                                                
                                          
                                                                        
                                                                                                                      
      
 
                            
                                                  



                                                                         
                                    
                               
                          
                             
                     
                                            
 
                      




                                         
                                            
 
                        




                                             
                                            
 
                           




                                          
                                            
 
                       
                                         

                                                            


                                         
                                                            
                               
 

                                         
                                                                    



                                         
                                                                 

                               





                                                                






                                                     
                                     

          
                                    
                                                                                                                  







                                                 
                              
                                                                                                                 
                      






                                                                                     
 
                                   
                                                                                                                  


                                             


                                                 


                                             








                                                                                          

                                      
 
               
                                                                                                     





                                               
                    
                                                                                                           
                                 
                   
                                                                                                      





                                               
                         
                                                                                                                
 
 
                      
                                                                                                                 
 
                      
                                                                                                                 










                                        
 






                                            

                                
                                                    
                                                                                                           
                                                                        

                                               






                                                     
       

                              

                                                                                 











                                                                                                   










                                              
 



                                                                         
          

                                                        

                    
 



                             

                            










                            





                                                             

                                                                                    

                                    


                                                        
                

                                                                                    
 
                            
                                                                        





                                                                                             

                                                     

                                                                          
                                        

                                                                              

                               
 


                                                




                                                                     

                                    
                                             
                               
         
                                              
                               
         
                                              
                                           














                                                                   

          
                           








                                                


                                       


                                                
 










                                                               





































                                                             
 









                                                       







                                                                                       


                                             






                                                      









                                                      
                 
                                         
 
                            

                                       
                              




                  


















                                           
                              
                                               

                                



                                                                             

                                                                       
                                                                                                                                     
               
                   
        
 



















                                     
                                 
                  
                                  
                           



                








                                                                                             
                               
                  




















                                     







                                   
                                      


                                                        

                                                  







                                                     



                                         



                                            
                                                    
                          

                                                    



                                                

                                              




                         
        













                                                                            






                                                                    
































                                                              


                                                       

                                             


                                                   

                                       
                                       
                                          




                                                  
                                                     


                      
            
                          
              
                          
              

                          
                      

                      




                           
 
                                                                         
                                                                                   

                                                                           
                                                                           
                               

                                       
                                           
                                               
                                         
                                              

                                               
                                          
                                                      
                                                                            
                                                                     
































                                                  





                                                                                                  
 



                                            


                                  



                                                

                                                                    
                                                              

                                                         
                                          
                

        




                                                      
                                          
 
                                         
            



                                         




                                                 


                                                                  
                 
                                        
        
 
                                                   
                                                                  
                                                   



                                                                  

                           


                                  

                                                 
                                                                                            
            








                                                                                         

       







                                                              



                                                           







                                                                                       


                                                 
                                                      





                                                         
                                                 

                                                  





                          




                                   

                                     



                                      

                                        
















                                                                          
 





                                   
                      










                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

                      










                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                       










                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
                       











                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                       










                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                       










                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
 







                                                                                                                                                                                                                                                                                                                          






                                                                                                                                                                                                                                                                                                                                 
                       






                                                                                                                                                                                                                                                                                                                                 
 




































                                                                





























































































                                                                                                             






                                                                                                












                                                                                                        







                                                                












































                                                                            





















                                         
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2018. 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_test_lib).

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

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

-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
-define(DEFAULT_CURVE, secp256r1).

%% For now always run locally
run_where(_) ->
    ClientNode = node(),
    ServerNode = node(),
    Host = rpc:call(ServerNode, net_adm, localhost, []),
    {ClientNode, ServerNode, Host}.

run_where(_, ipv6) ->
    ClientNode = node(),
    ServerNode = node(),
    Host = rpc:call(ServerNode, net_adm, localhost, []),
    {ClientNode, ServerNode, Host}.

node_to_hostip(Node, Role) ->
    [_ , Host] = string:tokens(atom_to_list(Node), "@"),
    {ok, Address} = inet:getaddr(Host, inet),
    %% Convert client addresses in 127.0.0.0/24 subnet to the atom 'localhost'.
    %% This is a workaround for testcase problems caused by the fact that
    %% inet:peername/1 and inet:getaddr/2 return different addresses when
    %% running on localhost.
    normalize_loopback(Address, Role).

normalize_loopback({127,_,_,_}, client) ->
    localhost;
normalize_loopback(Address, _) ->
    Address.

start_server(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node, ?MODULE, run_server, [Args]),
    receive
	{listen, up} ->
	    Result
    end.

run_server(Opts) ->
    Port = proplists:get_value(port, Opts),
    Options = proplists:get_value(options, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport =  proplists:get_value(transport, Opts, ssl),
    ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]),
    {ok, ListenSocket} = Transport:listen(Port, Options),
    Pid ! {listen, up},
    send_selected_port(Pid, Port, ListenSocket),
    run_server(ListenSocket, Opts).

run_server(ListenSocket, Opts) ->
    Accepters = proplists:get_value(accepters, Opts, 1),
    run_server(ListenSocket, Opts, Accepters). 
    
run_server(ListenSocket, Opts, 1) ->
    do_run_server(ListenSocket, connect(ListenSocket, Opts), Opts);
run_server(ListenSocket, Opts, N) ->
    Pid = proplists:get_value(from, Opts),
    Server = spawn(?MODULE, run_server, [ListenSocket, Opts, 1]),
    Pid ! {accepter, N, Server},
    run_server(ListenSocket, Opts, N-1).

do_run_server(_, {error, _} = Result, Opts)  ->
    ct:log("Server error result ~p~n", [Result]),
    Pid = proplists:get_value(from, Opts),
    Pid ! {self(), Result};
do_run_server(_, ok = Result, Opts) ->
    ct:log("Server cancel result ~p~n", [Result]),
    Pid = proplists:get_value(from, Opts),
    Pid ! {self(), Result};
do_run_server(ListenSocket, AcceptSocket, Opts) ->
    Pid = proplists:get_value(from, Opts),
    Transport = proplists:get_value(transport, Opts, ssl),
    {Module, Function, Args} = proplists:get_value(mfa, Opts),
    ct:log("~p:~p~nServer: apply(~p,~p,~p)~n",
           [?MODULE,?LINE, Module, Function, [AcceptSocket | Args]]),
    case apply(Module, Function, [AcceptSocket | Args]) of
	no_result_msg ->
	    ok;
	Msg ->
	    ct:log("~p:~p~nServer Msg: ~p ~n", [?MODULE,?LINE, Msg]),
	    Pid ! {self(), Msg}
    end,
    receive
	listen ->
	    run_server(ListenSocket, Opts);
	{listen, MFA} ->
	    run_server(ListenSocket, [MFA | proplists:delete(mfa, Opts)]);
	close ->
	    ct:log("~p:~p~nServer closing  ~p ~n", [?MODULE,?LINE, self()]),
	    Result = Transport:close(AcceptSocket),
	    Result1 = Transport:close(ListenSocket),
	    ct:log("~p:~p~nResult ~p : ~p ~n", [?MODULE,?LINE, Result, Result1]);
	{ssl_closed, _} ->
	    ok
    end.

%%% To enable to test with s_client -reconnect
connect(#sslsocket{} = ListenSocket, Opts) ->
    Node = proplists:get_value(node, Opts),
    ReconnectTimes =  proplists:get_value(reconnect_times, Opts, 0),
    Timeout = proplists:get_value(timeout, Opts, infinity),
    SslOpts = proplists:get_value(ssl_extra_opts, Opts, []),
    ContOpts = proplists:get_value(continue_options, Opts, []),
    AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout, SslOpts, ContOpts),
    case ReconnectTimes of
	0 ->
	    AcceptSocket;
	_ ->
	  remove_close_msg(ReconnectTimes),
	  AcceptSocket
    end;
connect(ListenSocket, _Opts) ->
    ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]),
    {ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
    AcceptSocket.

connect(_, _, 0, AcceptSocket, _, _, _) ->
    AcceptSocket;
connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
    ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]),

    case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
	{ok, Socket0, Ext} ->
            ct:log("Ext ~p:~n", [Ext]),            
            ct:log("~p:~p~nssl:handshake_cancel(~p)~n", [?MODULE,?LINE, Socket0]),            
            ssl:handshake_cancel(Socket0);
        Result ->
	    ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]),
	    Result
    end;
connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
    ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]),

    case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
	{ok, Socket0, Ext} ->
            [_|_] = maps:get(sni, Ext),
            ct:log("Ext ~p:~n", [Ext]),            
            ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),            
            case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
                {ok, Socket} ->
                    connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts);
                Error ->
                    ct:log("~p:~p~nssl:handshake_continue@~p ret ~p",[?MODULE,?LINE, Node,Error]),
                    Error
            end;
	Result ->
	    ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]),
	    Result
    end;
connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) ->
    ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, Timeout]),

    case ssl:handshake(AcceptSocket, Timeout) of
	{ok, Socket} ->
	    connect(ListenSocket, Node, N-1, Socket, Timeout, [], ContOpts);
	Result ->
	    ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]),
	    Result
    end;
connect(ListenSocket, _Node, _, _, Timeout, Opts, _) ->
    ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    ct:log("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
    ssl:handshake(AcceptSocket, Opts, Timeout),
    AcceptSocket.


start_server_transport_abuse_socket(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node, ?MODULE, transport_accept_abuse, [Args]),
    receive
	{listen, up} ->
	    Result
    end.

start_server_transport_control(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node, ?MODULE, transport_switch_control, [Args]),
    receive
	{listen, up} ->
	    Result
    end.

start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) ->
    process_flag(trap_exit, true),
    ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
    ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
    ClientOpts = ErlangClientOpts ++ ClientOpts0,

    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),

    Data = "From openssl to erlang",

    Port = ssl_test_lib:inet_port(node()),
    CaCertFile = proplists:get_value(cacertfile, ServerOpts),
    CertFile = proplists:get_value(certfile, ServerOpts),
    KeyFile = proplists:get_value(keyfile, ServerOpts),
    Version = ssl_test_lib:protocol_version(Config),

    Exe = "openssl",
    Args = case OpensslServerOpts of
	       [] -> 
		   ["s_server", "-accept", 
		    integer_to_list(Port), ssl_test_lib:version_flag(Version),
                    "-CAfile", CaCertFile,
		    "-cert", CertFile,"-key", KeyFile];
	       [Opt, Value] ->
		   ["s_server", Opt, Value, "-accept", 
		    integer_to_list(Port), ssl_test_lib:version_flag(Version),
                    "-CAfile", CaCertFile,
		    "-cert", CertFile,"-key", KeyFile]
	   end,
		   
    OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),  

    ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),

    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
                    {host, Hostname},
                    {from, self()},
                    {mfa, {?MODULE,
                           active_recv, [length(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).


transport_accept_abuse(Opts) ->
    Port = proplists:get_value(port, Opts),
    Options = proplists:get_value(options, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport =  proplists:get_value(transport, Opts, ssl),
    ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]),
    {ok, ListenSocket} = Transport:listen(Port, Options),
    Pid ! {listen, up},
    send_selected_port(Pid, Port, ListenSocket),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    {error, _} = ssl:connection_information(AcceptSocket),
    _ = ssl:handshake(AcceptSocket, infinity),
    Pid ! {self(), ok}.

start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) ->
    process_flag(trap_exit, true),
    ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
    ServerOpts = ErlangServerOpts ++  ServerOpts0,

    {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),


    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
                    {from, self()},
                    {mfa, {?MODULE, active_recv, [length(Data)]}},
                    {options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    Version = ssl_test_lib:protocol_version(Config),
   
    Exe = "openssl",
    Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg",  "-connect", 
                                                 hostname_format(Hostname) ++ ":" ++ integer_to_list(Port),
                                                 ssl_test_lib:version_flag(Version)],

    OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),

    Callback(Server, OpenSslPort),

    ssl_test_lib:close(Server),

    ssl_test_lib:close_port(OpenSslPort),
    process_flag(trap_exit, false).

transport_switch_control(Opts) ->
    Port = proplists:get_value(port, Opts),
    Options = proplists:get_value(options, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport =  proplists:get_value(transport, Opts, ssl),
    ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]),
    {ok, ListenSocket} = Transport:listen(Port, Options),
    Pid ! {listen, up},
    send_selected_port(Pid, Port, ListenSocket),
    {ok, AcceptSocket} = ssl:transport_accept(ListenSocket),    
    ok = ssl:controlling_process(AcceptSocket, self()),
    Pid ! {self(), ok}.


remove_close_msg(0) ->
    ok;
remove_close_msg(ReconnectTimes) ->
    receive
	{ssl_closed, _} ->
	   remove_close_msg(ReconnectTimes -1)
    end.
	    
start_client(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node, ?MODULE, run_client_init, [lists:delete(return_socket, Args)]),
    receive 
	{connected, Socket} ->
	    case lists:member(return_socket, Args) of
		true -> {Result, Socket};
		false -> Result
	    end;
	{connect_failed, Reason} ->
	    {connect_failed, Reason}
    end.

run_client_init(Opts) ->
    put(retries, 0),
    run_client(Opts).

run_client(Opts) ->
    Node = proplists:get_value(node, Opts),
    Host = proplists:get_value(host, Opts),
    Port = proplists:get_value(port, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport =  proplists:get_value(transport, Opts, ssl),
    Options = proplists:get_value(options, Opts),
    ContOpts = proplists:get_value(continue_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 ContOpts of
        [] ->
            client_loop(Node, Host, Port, Pid, Transport, Options, Opts);
        _ ->
            client_cont_loop(Node, Host, Port, Pid, Transport, Options, ContOpts, Opts)
    end.

client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
    case Transport:connect(Host, Port, Options) of
	{ok, Socket} ->
	    Pid ! {connected, Socket},
	    ct:log("~p:~p~nClient: connected~n", [?MODULE,?LINE]),
	    %% In special cases we want to know the client port, it will
	    %% be indicated by sending {port, 0} in options list!
	    send_selected_port(Pid,  proplists:get_value(port, Options), Socket),
	    {Module, Function, Args} = proplists:get_value(mfa, Opts),
	    ct:log("~p:~p~nClient: apply(~p,~p,~p)~n",
			       [?MODULE,?LINE, Module, Function, [Socket | Args]]),
	    case apply(Module, Function, [Socket | Args]) of
		no_result_msg ->
		    ok;
		Msg ->
		    ct:log("~p:~p~nClient Msg: ~p ~n", [?MODULE,?LINE, Msg]),
		    Pid ! {self(), Msg}
	    end,
	    receive
		close ->
		    ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]),
		    Transport:close(Socket);
		{ssl_closed, Socket} ->
		    ok;
		{gen_tcp, closed} ->
		    ok
	    end;
	{error, econnrefused = Reason} ->
	    case get(retries) of
		N when N < 5 ->
		    ct:log("~p:~p~neconnrefused 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, 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}
    end.

client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->
    case Transport:connect(Host, Port, Options) of
        {ok, Socket, _} ->
           Result = Transport:handshake_cancel(Socket),
            ct:log("~p:~p~nClient: Cancel: ~p ~n", [?MODULE,?LINE, Result]),
            Pid ! {connect_failed, Result};
        {error, Reason} ->
	    ct:log("~p:~p~nClient: connection failed: ~p ~n", [?MODULE,?LINE, Reason]),
	    Pid ! {connect_failed, Reason}
    end;

client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) ->
    case Transport:connect(Host, Port, Options) of
        {ok, Socket0, _} ->
            ct:log("~p:~p~nClient: handshake_continue(~p, ~p, infinity) ~n", [?MODULE, ?LINE, Socket0, ContOpts]),
            case Transport:handshake_continue(Socket0, ContOpts) of
                {ok, Socket} ->
                    Pid ! {connected, Socket},
                    {Module, Function, Args} = proplists:get_value(mfa, Opts),
                    ct:log("~p:~p~nClient: apply(~p,~p,~p)~n",
                           [?MODULE,?LINE, Module, Function, [Socket | Args]]),
                    case apply(Module, Function, [Socket | Args]) of
                        no_result_msg ->
                            ok;
                        Msg ->
                            ct:log("~p:~p~nClient Msg: ~p ~n", [?MODULE,?LINE, Msg]),
                            Pid ! {self(), Msg}
                    end
	    end;
        {error, Reason} ->
            ct:log("~p:~p~nClient: connection failed: ~p ~n", [?MODULE,?LINE, Reason]),
            Pid ! {connect_failed, Reason}
    end.
            
close(Pid) ->
    ct:log("~p:~p~nClose ~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]) 
		
    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) ->
    {ClientIP, ClientPort} = get_ip_port(ServerMsg),
    receive 
	{Server, ServerMsg} ->
	    check_result(Client, ClientMsg);
        %% Workaround to accept local addresses (127.0.0.0/24)
        {Server, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost  ->
            check_result(Client, ClientMsg);
	{Client, ClientMsg} ->
	    check_result(Server, ServerMsg);
	{Port, {data,Debug}} when is_port(Port) ->
	    ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]),
	    check_result(Server, ServerMsg, Client, ClientMsg);
        {Port,closed} when is_port(Port) ->
            ct:log("~p:~p~n Openssl port closed ~n",[?MODULE,?LINE]),
            check_result(Server, ServerMsg, Client, ClientMsg);
        {'EXIT', epipe} ->
            ct:log("~p:~p~n Openssl port died ~n",[?MODULE,?LINE]),
            check_result(Server, ServerMsg, Client, ClientMsg);
	Unexpected ->
	    Reason = {{expected, {Client, ClientMsg}},
		      {expected, {Server, ServerMsg}}, {got, Unexpected}},
	    ct:fail(Reason)
    end.

check_result(Pid, Msg) ->
    {ClientIP, ClientPort} = get_ip_port(Msg),
    receive 
	{Pid, Msg} -> 
	    ok;
        %% Workaround to accept local addresses (127.0.0.0/24)
        {Pid, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost ->
            ok;
	{Port, {data,Debug}} when is_port(Port) ->
	    ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]),
	    check_result(Pid,Msg);
        {Port,closed} when is_port(Port)->
            ct:log("~p:~p Openssl port closed ~n",[?MODULE,?LINE]),
            check_result(Pid, Msg);
	Unexpected ->
	    Reason = {{expected, {Pid, Msg}}, 
		      {got, Unexpected}},
	    ct:fail(Reason)
    end.


get_ip_port({ok,{ClientIP, ClientPort}}) ->
    {ClientIP, ClientPort};
get_ip_port(_) ->
    {undefined, undefined}.


check_server_alert(Pid, Alert) ->
    receive
	{Pid, {error, {tls_alert, {Alert, STxt}}}} ->
            check_server_txt(STxt),
            ok;
        {Pid, {error, closed}} ->
            ok
    end.
check_server_alert(Server, Client, Alert) ->
    receive
	{Server, {error, {tls_alert, {Alert, STxt}}}} ->
            check_server_txt(STxt),
            check_client_alert(Client, Alert)
    end.
check_client_alert(Pid, Alert) ->
    receive
	{Pid, {error, {tls_alert, {Alert, CTxt}}}} ->
            check_client_txt(CTxt),
            ok;
        {Pid, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} ->
            check_client_txt(CTxt),
            ok;
        {Pid, {error, closed}} ->
            ok
    end.
check_client_alert(Server, Client, Alert) ->
    receive
	{Client, {error, {tls_alert, {Alert, CTxt}}}} ->
            check_client_txt(CTxt),
            check_server_alert(Server, Alert);
        {Client, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} ->
            check_client_txt(CTxt),
            ok;
        {Client, {error, closed}} ->
            ok
    end.
check_server_txt("TLS server" ++ _) ->
    ok;
check_server_txt("DTLS server" ++ _) ->
    ok;
check_server_txt(Txt) ->
    ct:fail({expected_server, {got, Txt}}).

check_client_txt("TLS client" ++ _) ->
    ok;
check_client_txt("DTLS client" ++ _) ->
    ok;
check_client_txt(Txt) ->
    ct:fail({expected_server, {got, Txt}}).

wait_for_result(Server, ServerMsg, Client, ClientMsg) -> 
    receive 
	{Server, ServerMsg} -> 
	    receive 
		{Client, ClientMsg} ->
		    ok
		%% Unexpected ->
		%%     Unexpected
	    end;
	{Client, ClientMsg} -> 
	    receive 
		{Server, ServerMsg} ->
		    ok
		%% Unexpected ->
		%%     Unexpected
	    end;
	{Port, {data,Debug}} when is_port(Port) ->
	    ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]),
	    wait_for_result(Server, ServerMsg, Client, ClientMsg)
	%% Unexpected ->
	%%     Unexpected
    end.

check_ok([]) ->
    ok;
check_ok(Pids) ->
    receive 
	{Pid, ok} ->
	    check_ok(lists:delete(Pid, Pids));
	Other ->
	    ct:fail({expected, {"pid()", ok}, got, Other})
    end.
	
wait_for_result(Pid, Msg) -> 
    receive 
	{Pid, Msg} -> 
	    ok;
	{Port, {data,Debug}} when is_port(Port) ->
	    ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]),
	    wait_for_result(Pid,Msg)
	%% Unexpected ->
	%%     Unexpected
    end.

cert_options(Config) ->
    ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				      "client", "cacerts.pem"]),
    ClientCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				    "client", "cert.pem"]),
    ClientCertFileDigitalSignatureOnly = filename:join([proplists:get_value(priv_dir, Config),
				    "client", "digital_signature_only_cert.pem"]),
    ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				      "server", "cacerts.pem"]),
    ServerCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				    "server", "cert.pem"]),
    ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config), 
			     "server", "key.pem"]),
    ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config), 
			     "client", "key.pem"]),
    ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				       "server", "keycert.pem"]),
    ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				       "client", "keycert.pem"]),

    BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				   "badcacert.pem"]),
    BadCertFile = filename:join([proplists:get_value(priv_dir, Config), 
				   "badcert.pem"]),
    BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), 
			      "badkey.pem"]),
    
    [{client_opts, [{cacertfile, ClientCaCertFile}, 
		    {certfile, ClientCertFile},  
		    {keyfile, ClientKeyFile}]}, 
     {client_verification_opts, [{cacertfile, ServerCaCertFile}, 
				{certfile, ClientCertFile},  
				{keyfile, ClientKeyFile},
				{verify, verify_peer}]}, 
     {client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile},
				{certfile, ClientCertFileDigitalSignatureOnly},
				{keyfile, ClientKeyFile},
				{ssl_imp, new}]},
     {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, 
		    {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
     {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, 
		    {cacertfile, ClientCaCertFile},
		    {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
     {client_kc_opts, [{certfile, ClientKeyCertFile},  {ssl_imp, new}]}, 
     {server_kc_opts, [{ssl_imp, new},{reuseaddr, true}, 
		       {certfile, ServerKeyCertFile}]},
     {client_bad_ca, [{cacertfile, BadCaCertFile}, 
		      {certfile, ClientCertFile},
		      {keyfile, ClientKeyFile},
		      {ssl_imp, new}]},
     {client_bad_cert, [{cacertfile, ClientCaCertFile},	    
			{certfile, BadCertFile},  
			{keyfile, ClientKeyFile},
			{ssl_imp, new}]},
     {server_bad_ca, [{ssl_imp, new},{cacertfile, BadCaCertFile},
		      {certfile, ServerCertFile}, 
		      {keyfile, ServerKeyFile}]},
     {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
			{certfile, BadCertFile}, {keyfile, ServerKeyFile}]},
     {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
		       {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
     | Config].


make_dsa_cert(Config) ->  
    CryptoSupport = crypto:supports(),
    case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of
        true ->
            ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
            ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
            CertChainConf = gen_conf(dsa, dsa, ClientChain, ServerChain),
            ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa"]),
            ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa"]),
            GenCertData = public_key:pkix_test_data(CertChainConf),
            [{server_config, ServerConf}, 
             {client_config, ClientConf}] = 
                x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
            
          [{server_dsa_opts, ServerConf},
           {server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]},
           {client_dsa_opts, ClientConf}
           | Config];
      false ->
          Config
  end.


make_cert_chains_der(Alg, UserConf) ->
    ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
    ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
    CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain),
    public_key:pkix_test_data(CertChainConf).

make_cert_chains_pem(Alg, UserConf, Config, Suffix) ->
    ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
    ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
    CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain),
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
    GenCertData = public_key:pkix_test_data(CertChainConf),
    Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
    CConf = proplists:get_value(client_config, Conf),
    SConf = proplists:get_value(server_config, Conf),
    #{server_config => SConf,
      client_config => CConf}.

make_rsa_cert_chains(UserConf, Config, Suffix) ->
    ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
    ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
    CertChainConf = gen_conf(rsa, rsa, ClientChain, ServerChain),
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa" ++ Suffix]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa" ++ Suffix]),
    GenCertData = public_key:pkix_test_data(CertChainConf),
    [{server_config, ServerConf}, 
     {client_config, ClientConf}] = 
        x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
    {[{verify, verify_peer} | ClientConf],
     [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
    }.

make_ecc_cert_chains(UserConf, Config, Suffix) ->
    ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
    ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
    CertChainConf = gen_conf(ecdsa, ecdsa, ClientChain, ServerChain),
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa" ++ Suffix]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa" ++ Suffix]),
    GenCertData = public_key:pkix_test_data(CertChainConf),
    [{server_config, ServerConf}, 
     {client_config, ClientConf}] = 
        x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
    {[{verify, verify_peer} | ClientConf],
     [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
    }.


make_dsa_cert_chains(UserConf, Config, Suffix) ->  
    CryptoSupport = crypto:supports(),
    case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of
        true ->
            ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
            ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
            CertChainConf = gen_conf(dsa, dsa, ClientChain, ServerChain),
            ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa" ++ Suffix]),
            ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa" ++ Suffix]),
            GenCertData = public_key:pkix_test_data(CertChainConf),
            [{server_config, ServerConf}, 
             {client_config, ClientConf}] = 
                x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
            {[{verify, verify_peer} | ClientConf],
             [{reuseaddr, true}, {verify, verify_peer} | ServerConf]};
      false ->
          Config
  end.

make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config) ->
    make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, ?DEFAULT_CURVE).
%%
make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) ->
    ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
    ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
    CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain, Curve),
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ClientChainType)]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ServerChainType)]),
    GenCertData = public_key:pkix_test_data(CertChainConf),
    [{server_config, ServerConf}, 
     {client_config, ClientConf}] = 
        x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
    {[{verify, verify_peer} | ClientConf],
     [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
    }.

default_cert_chain_conf() ->
    %% Use only default options
    [[],[],[]].


gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) ->
    gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE).
%%
gen_conf(mix, mix, UserClient, UserServer, _) ->
    ClientTag = conf_tag("client"),
    ServerTag = conf_tag("server"),

    DefaultClient = default_cert_chain_conf(), 
    DefaultServer = default_cert_chain_conf(),
    
    ClientConf = merge_chain_spec(UserClient, DefaultClient, []),
    ServerConf = merge_chain_spec(UserServer, DefaultServer, []),
    
    new_format([{ClientTag, ClientConf}, {ServerTag, ServerConf}]);
gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, Curve) ->
    ClientTag = conf_tag("client"),
    ServerTag = conf_tag("server"),

    DefaultClient = chain_spec(client, ClientChainType, Curve),
    DefaultServer = chain_spec(server, ServerChainType, Curve),
    
    ClientConf = merge_chain_spec(UserClient, DefaultClient, []),
    ServerConf = merge_chain_spec(UserServer, DefaultServer, []),
    
    new_format([{ClientTag, ClientConf}, {ServerTag, ServerConf}]).

new_format(Conf) ->
    CConf = proplists:get_value(client_chain, Conf),
    SConf = proplists:get_value(server_chain, Conf),
    #{server_chain => proplist_to_map(SConf),
      client_chain => proplist_to_map(CConf)}.

proplist_to_map([Head | Rest]) -> 
    [Last | Tail] = lists:reverse(Rest),
    #{root => Head,
      intermediates => lists:reverse(Tail),
      peer => Last}.

conf_tag(Role) ->
    list_to_atom(Role ++ "_chain").

chain_spec(_Role, ecdh_rsa, Curve) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    CurveOid = pubkey_cert_records:namedCurves(Curve),
     [[Digest, {key, {namedCurve, CurveOid}}],
      [Digest, {key, hardcode_rsa_key(1)}],
      [Digest, {key, {namedCurve, CurveOid}}]];

chain_spec(_Role, ecdhe_ecdsa, Curve) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    CurveOid = pubkey_cert_records:namedCurves(Curve),
    [[Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}]];

chain_spec(_Role, ecdh_ecdsa, Curve) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    CurveOid = pubkey_cert_records:namedCurves(Curve),
    [[Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}]];
chain_spec(_Role, ecdhe_rsa, _) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    [[Digest, {key, hardcode_rsa_key(1)}],
     [Digest, {key, hardcode_rsa_key(2)}],
     [Digest, {key, hardcode_rsa_key(3)}]];
chain_spec(_Role, ecdsa, Curve) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    CurveOid = pubkey_cert_records:namedCurves(Curve),
    [[Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}],
     [Digest, {key, {namedCurve, CurveOid}}]];
chain_spec(_Role, rsa, _) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    [[Digest, {key, hardcode_rsa_key(1)}],
                                      [Digest, {key, hardcode_rsa_key(2)}],
                                      [Digest, {key, hardcode_rsa_key(3)}]];
chain_spec(_Role, dsa, _) ->
    Digest = {digest, appropriate_sha(crypto:supports())},
    [[Digest, {key, hardcode_dsa_key(1)}],
     [Digest, {key, hardcode_dsa_key(2)}],
     [Digest, {key, hardcode_dsa_key(3)}]].

merge_chain_spec([], [], Acc)->
    lists:reverse(Acc);
merge_chain_spec([User| UserRest], [Default | DefaultRest], Acc) ->
    Merge = merge_spec(User, Default, confs(), []),
    merge_chain_spec(UserRest, DefaultRest, [Merge | Acc]).

confs() ->
    [key, digest, validity, extensions].

merge_spec(_, _, [], Acc) ->
    Acc;
merge_spec(User, Default, [Conf | Rest], Acc) ->
    case proplists:get_value(Conf, User, undefined) of
        undefined ->
            case proplists:get_value(Conf, Default, undefined) of
                undefined ->
                    merge_spec(User, Default, Rest, Acc);
                Value  ->
                    merge_spec(User, Default, Rest, [{Conf, Value} | Acc])
            end;
        Value ->
                merge_spec(User, Default, Rest, [{Conf, Value} | Acc])
    end.

make_mix_cert(Config) ->
    Ext = x509_test:extensions([{key_usage, [digitalSignature]}]),
    Digest = {digest, appropriate_sha(crypto:supports())},
    CurveOid = pubkey_cert_records:namedCurves(?DEFAULT_CURVE),
    Mix = proplists:get_value(mix, Config, peer_ecc),
    ClientChainType =ServerChainType = mix,
    {ClientChain, ServerChain} = mix(Mix, Digest, CurveOid, Ext),
    CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain),
    ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "mix" ++ atom_to_list(Mix)]),
    ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "mix" ++ atom_to_list(Mix)]),
    GenCertData = public_key:pkix_test_data(CertChainConf),
    [{server_config, ServerConf}, 
     {client_config, ClientConf}] = 
        x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
    {[{verify, verify_peer} | ClientConf],
     [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
    }.

mix(peer_ecc, Digest, CurveOid, Ext) ->
    ClientChain =  [[Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key, hardcode_rsa_key(1)}], 
                    [Digest, {key, {namedCurve, CurveOid}}, {extensions, Ext}]
                   ],
    ServerChain =  [[Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key,  hardcode_rsa_key(2)}], 
                    [Digest, {key, {namedCurve, CurveOid}},{extensions, Ext}]
                   ],
    {ClientChain, ServerChain};

mix(peer_rsa, Digest, CurveOid, Ext) ->
    ClientChain =  [[Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key, hardcode_rsa_key(1)}, {extensions, Ext}]
                   ],
    ServerChain =  [[Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key, {namedCurve, CurveOid}}], 
                    [Digest, {key,  hardcode_rsa_key(2)},{extensions, Ext}]
                   ],
    {ClientChain, ServerChain}.

make_ecdsa_cert(Config) ->
    CryptoSupport = crypto:supports(),
    case proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)) of
        true ->
            ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa"]),
            ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa"]),
            ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
            ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
            CertChainConf = gen_conf(ecdsa, ecdsa, ClientChain, ServerChain),
            GenCertData = public_key:pkix_test_data(CertChainConf),
            [{server_config, ServerConf}, 
             {client_config, ClientConf}] = 
                x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
	    [{server_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
             
	     {server_ecdsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
					 {verify, verify_peer} | ServerConf]},
	     {client_ecdsa_opts, ClientConf}
	     | Config];
	false ->
	    Config
    end.
make_rsa_cert(Config) ->
    CryptoSupport = crypto:supports(),
    case proplists:get_bool(rsa, proplists:get_value(public_keys, CryptoSupport)) of
        true ->
            ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa"]),
            ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa"]),
            ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
            ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
            CertChainConf = gen_conf(rsa, rsa, ClientChain, ServerChain),
            GenCertData = public_key:pkix_test_data(CertChainConf),
            [{server_config, ServerConf}, 
             {client_config, ClientConf}] = 
                x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),               
	    [{server_rsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
             
	     {server_rsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
					 {verify, verify_peer} | ServerConf]},
	     {client_rsa_opts, ClientConf},
             {client_rsa_verify_opts,  [{verify, verify_peer} |ClientConf]}
	     | Config];
	false ->
	    Config
    end.
appropriate_sha(CryptoSupport) ->
    Hashes = proplists:get_value(hashs, CryptoSupport),
    case lists:member(sha256, Hashes) of
	true ->
	    sha256;
	false ->
	    sha1
    end.

%% RFC 4492, Sect. 2.3.  ECDH_RSA
%%
%%    This key exchange algorithm is the same as ECDH_ECDSA except that the
%%    server's certificate MUST be signed with RSA rather than ECDSA.
make_ecdh_rsa_cert(Config) ->
    CryptoSupport = crypto:supports(),
    case proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)) of
	true ->
            ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdh_rsa"]),
            ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdh_rsa"]),
            ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
            ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
            CertChainConf = gen_conf(ecdh_rsa, ecdh_rsa, ClientChain, ServerChain),
            GenCertData = public_key:pkix_test_data(CertChainConf),
            [{server_config, ServerConf}, 
             {client_config, ClientConf}] = 
                x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),

	    [{server_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
				    
	     {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, 	 
                                            {verify, verify_peer} | ServerConf]},
					
	     {client_ecdh_rsa_opts, ClientConf}
				   
             | Config];
	_ ->
	    Config
    end.

start_upgrade_server(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node, ?MODULE, run_upgrade_server, [Args]),
    receive
	{listen, up} ->
	    Result
    end.

run_upgrade_server(Opts) ->
    Port = proplists:get_value(port, Opts),
    TimeOut = proplists:get_value(timeout, Opts, infinity),
    TcpOptions = proplists:get_value(tcp_options, Opts),
    SslOptions = proplists:get_value(ssl_options, Opts),
    Pid = proplists:get_value(from, Opts),

    ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]),
    {ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
    Pid ! {listen, up},
    send_selected_port(Pid, Port, ListenSocket),
    ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]),
    {ok, AcceptSocket} = gen_tcp:accept(ListenSocket),

    try
	{ok, SslAcceptSocket} = case TimeOut of
				    infinity ->
					ct:log("~p:~p~nssl:handshake(~p, ~p)~n",
							   [?MODULE,?LINE, AcceptSocket, SslOptions]),
					ssl:handshake(AcceptSocket, SslOptions);
				    _ ->
					ct:log("~p:~p~nssl:handshake(~p, ~p, ~p)~n",
							   [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]),
					ssl:handshake(AcceptSocket, SslOptions, TimeOut)
				end,
	{Module, Function, Args} = proplists:get_value(mfa, Opts),
	Msg = apply(Module, Function, [SslAcceptSocket | Args]),
	ct:log("~p:~p~nUpgrade Server Msg: ~p ~n", [?MODULE,?LINE, Msg]),
	Pid ! {self(), Msg},
	receive
	    close ->
		ct:log("~p:~p~nUpgrade Server closing~n", [?MODULE,?LINE]),
		ssl:close(SslAcceptSocket)
	end
    catch error:{badmatch, Error} ->
	    Pid ! {self(), Error}
    end.

start_upgrade_client(Args) ->
    Node = proplists:get_value(node, Args),
    spawn_link(Node, ?MODULE, run_upgrade_client, [Args]).

run_upgrade_client(Opts) ->
    Host = proplists:get_value(host, Opts),
    Port = proplists:get_value(port, Opts),
    Pid = proplists:get_value(from, Opts),
    TcpOptions = proplists:get_value(tcp_options, Opts),
    SslOptions = proplists:get_value(ssl_options, Opts),
    
    ct:log("~p:~p~ngen_tcp:connect(~p, ~p, ~p)~n",
		       [?MODULE,?LINE, Host, Port, TcpOptions]),
    {ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions),

    send_selected_port(Pid, Port, Socket),

    ct:log("~p:~p~nssl:connect(~p, ~p)~n", [?MODULE,?LINE, Socket, SslOptions]),
    {ok, SslSocket} = ssl:connect(Socket, SslOptions),

    {Module, Function, Args} = proplists:get_value(mfa, Opts),
    ct:log("~p:~p~napply(~p, ~p, ~p)~n",
		       [?MODULE,?LINE, Module, Function, [SslSocket | Args]]),
    Msg = apply(Module, Function, [SslSocket | Args]),
    ct:log("~p:~p~nUpgrade Client Msg: ~p ~n", [?MODULE,?LINE, Msg]),
    Pid ! {self(), Msg},
    receive 
	close ->
	    ct:log("~p:~p~nUpgrade Client closing~n", [?MODULE,?LINE]),
	    ssl:close(SslSocket)
    end.

start_upgrade_server_error(Args) ->
    Node = proplists:get_value(node, Args),
    Result = spawn_link(Node,?MODULE, run_upgrade_server_error, [Args]),
    receive
	{listen, up} ->
	    Result
    end.

run_upgrade_server_error(Opts) ->
    Port = proplists:get_value(port, Opts),
    TimeOut = proplists:get_value(timeout, Opts, infinity),
    TcpOptions = proplists:get_value(tcp_options, Opts),
    SslOptions = proplists:get_value(ssl_options, Opts),
    Pid = proplists:get_value(from, Opts),

    ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]),
    {ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
    Pid ! {listen, up},
    send_selected_port(Pid, Port, ListenSocket),
    ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]),
    {ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
    Error = case TimeOut of
		infinity ->
		    ct:log("~p:~p~nssl:handshake(~p, ~p)~n",
				       [?MODULE,?LINE, AcceptSocket, SslOptions]),
		    ssl:handshake(AcceptSocket, SslOptions);
		_ ->
		    ct:log("~p:~p~nssl:ssl_handshake(~p, ~p, ~p)~n",
				       [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]),
		    ssl:handshake(AcceptSocket, SslOptions, TimeOut)
	    end,
    Pid ! {self(), Error}.

start_server_error(Args) ->
    Result = spawn_link(?MODULE, run_server_error, [Args]),
    receive 
	{listen, up} ->
	    Result
    end.

run_server_error(Opts) ->
    Port = proplists:get_value(port, Opts),
    Options = proplists:get_value(options, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport =  proplists:get_value(transport, Opts, ssl),
    ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]),
    case Transport:listen(Port, Options) of
	{ok, #sslsocket{} = ListenSocket} ->
	    %% To make sure error_client will
	    %% get {error, closed} and not {error, connection_refused}
	    Pid ! {listen, up},
	    send_selected_port(Pid, Port, ListenSocket),
	    ct:log("~p:~p~nssl:transport_accept(~p)~n", [?MODULE,?LINE, ListenSocket]),
	    case Transport:transport_accept(ListenSocket) of
		{error, _} = Error ->
		    Pid ! {self(), Error};
		{ok, AcceptSocket} ->
		    ct:log("~p:~p~nssl:ssl_accept(~p)~n", [?MODULE,?LINE, AcceptSocket]),
		    Error = ssl:handshake(AcceptSocket),
		    Pid ! {self(), Error}
	    end;
	{ok, ListenSocket} ->
	    Pid ! {listen, up},
	    send_selected_port(Pid, Port, ListenSocket),
	    ct:log("~p:~p~n~p:accept(~p)~n", [?MODULE,?LINE, Transport, ListenSocket]),
	     case Transport:accept(ListenSocket) of
		{error, _} = Error ->
		     Pid ! {self(), Error}
	     end;
	Error ->
	    %% Not really true but as this is an error test 
	    %% this is what we want.
	    Pid ! {listen, up},
	    Pid ! {self(), Error}
    end.

start_client_error(Args) ->
    Node = proplists:get_value(node, Args),
    spawn_link(Node, ?MODULE, run_client_error, [Args]).

run_client_error(Opts) ->
    Host = proplists:get_value(host, Opts),
    Port = proplists:get_value(port, Opts),
    Pid = proplists:get_value(from, Opts),
    Transport = proplists:get_value(transport, Opts, ssl),
    Options = proplists:get_value(options, Opts),
    ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]),
    Error = Transport:connect(Host, Port, Options),
    case Error of
        {error, _} ->
            Pid ! {self(), Error};
        {ok, _Socket} ->
            receive
                {ssl_error, _, {tls_alert, _}} = SslError ->
                                Pid ! {self(), SslError}
            end
    end.

accepters(N) ->
    accepters([], N).

accepters(Acc, 0) ->
    Acc;
accepters(Acc, N) ->
    receive 
	{accepter, _, Server} ->
	    accepters([Server| Acc], N-1)
    end.


basic_test(COpts, SOpts, Config) ->
    SType = proplists:get_value(server_type, Config),
    CType = proplists:get_value(client_type, Config),
    {Server, Port} = start_server(SType, COpts, SOpts, Config),
    Client = start_client(CType, Port, COpts, Config),
    gen_check_result(Server, SType, Client, CType),
    stop(Server, Client).    

basic_alert(ClientOpts, ServerOpts, Config, Alert) ->
    SType = proplists:get_value(server_type, Config),
    CType = proplists:get_value(client_type, Config),
    run_basic_alert(SType, CType, ClientOpts, ServerOpts, Config, Alert).

run_basic_alert(erlang, erlang, ClientOpts, ServerOpts, Config, Alert) ->
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    
    Server = start_server_error([{node, ServerNode}, {port, 0},
                                 {from, self()},
                                 {mfa, {ssl_test_lib, no_result, []}},
                                 {options, ServerOpts}]),
    
    Port  = inet_port(Server),
    
    Client = start_client_error([{node, ClientNode}, {port, Port},
					      {host, Hostname},
					      {from, self()},
                                              {mfa, {ssl_test_lib, no_result, []}},
					      {options, ClientOpts}]),

    check_server_alert(Server, Client, Alert);
run_basic_alert(openssl = SType, erlang, ClientOpts, ServerOpts, Config, Alert) ->
    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    {_Server, Port} = start_server(SType, ClientOpts, ServerOpts, Config),
    ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
    Client = start_client_error([{node, ClientNode}, {port, Port},
                                 {host, Hostname},
                                 {from, self()},
                                 {mfa, {ssl_test_lib, no_result, []}},
					      {options, ClientOpts}]),
    
    check_client_alert(Client, Alert);
run_basic_alert(erlang, openssl = CType, ClientOpts, ServerOpts, Config, Alert) ->
    {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server = start_server_error([{node, ServerNode}, {port, 0},
                                 {host, Hostname},
                                 {from, self()},
                                 {mfa, {ssl_test_lib, no_result, []}},
                                 {options, ServerOpts}]),
    Port  = inet_port(Server),
    start_client(CType, Port, ClientOpts, Config),

    check_server_alert(Server, Alert).
    

ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) ->
    {Server, Port} = start_server_ecc(erlang, SOpts, Expect, SECCOpts, Config),
    Client = start_client_ecc(erlang, Port, COpts, Expect, CECCOpts, Config),
    check_result(Server, ok, Client, ok),
    stop(Server, Client).

ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) ->
    {Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config),
    Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config),
    check_server_alert(Server, Client, insufficient_security).

start_basic_client(openssl, Version, Port, ClientOpts) ->
    Cert = proplists:get_value(certfile, ClientOpts),
    Key = proplists:get_value(keyfile, ClientOpts),
    CA = proplists:get_value(cacertfile, ClientOpts),
    Groups0 = proplists:get_value(groups, ClientOpts),
    Exe = "openssl",
    Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
	    ssl_test_lib:version_flag(Version),
	    "-CAfile", CA, "-host", "localhost", "-msg", "-debug"],
    Args1 =
       case Groups0 of
           undefined ->
               Args0;
           G ->
               Args0 ++ ["-groups", G]
       end,
    Args =
       case {Cert, Key} of
           {C, K} when C =:= undefined orelse
                       K =:= undefined ->
               Args1;
           {C, K} ->
               Args1 ++ ["-cert", C, "-key", K]
       end,

    OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
    true = port_command(OpenSslPort, "Hello world"),
    OpenSslPort.

start_client(openssl, Port, ClientOpts, Config) ->
    Version = ssl_test_lib:protocol_version(Config),
    Exe = "openssl",
    Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
    Groups0 = proplists:get_value(groups, ClientOpts),
    CertArgs = openssl_cert_options(ClientOpts, client),
    Exe = "openssl",
    Args0 =  case Groups0 of
                undefined ->
                    ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
                     ciphers(Ciphers, Version),
                     ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
                Group ->
                    ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
                     ciphers(Ciphers, Version), "-groups", Group, 
                     ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
            end,
    Args = maybe_force_ipv4(Args0),
    OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), 
    true = port_command(OpenSslPort, "Hello world"),
    OpenSslPort;

start_client(erlang, Port, ClientOpts, Config) ->
    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    KeyEx = proplists:get_value(check_keyex, Config, false),
    ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
			       {host, Hostname},
			       {from, self()},
			       {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}},
			       {options, ClientOpts}]).

%% Workaround for running tests on machines where openssl
%% s_client would use an IPv6 address with localhost. As
%% this test suite and the ssl application is not prepared
%% for that we have to force s_client to use IPv4 if
%% OpenSSL supports IPv6.
maybe_force_ipv4(Args0) ->
    case is_ipv6_supported() of
        true ->
            Args0 ++ ["-4"];
        false ->
            Args0
    end.

start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) ->
    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
                               {host, Hostname},
                               {from, self()},
                               {mfa, {?MODULE, check_ecc, [client, Expect]}},
                               {options,
                                ECCOpts ++
                                [{verify, verify_peer} | ClientOpts]}]).

start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) ->
    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
                                     {host, Hostname},
                                     {from, self()},
                                     {options,
                                      ECCOpts ++
                                      [{verify, verify_peer} | ClientOpts]}]).


start_server(openssl, ClientOpts, ServerOpts, Config) ->
    Port = inet_port(node()),
    Version = protocol_version(Config),
    Exe = "openssl",
    CertArgs = openssl_cert_options(ServerOpts, server),
    Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
    Groups0 = proplists:get_value(groups, ServerOpts),
    Args =  case Groups0 of
                undefined ->
                    ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
                     ciphers(Ciphers, Version),
                     ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
                Group ->
                       ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
                        ciphers(Ciphers, Version), "-groups", Group, 
                        ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
            end,
    OpenSslPort = portable_open_port(Exe, Args),
    true = port_command(OpenSslPort, "Hello world"),
    {OpenSslPort, Port};
start_server(erlang, _, ServerOpts, Config) ->
    {_, ServerNode, _} = ssl_test_lib:run_where(Config),
    KeyEx = proplists:get_value(check_keyex, Config, false),
    Versions = protocol_versions(Config),
    Server = start_server([{node, ServerNode}, {port, 0},
                           {from, self()},
                           {mfa, {ssl_test_lib,
                                  check_key_exchange_send_active,
                                  [KeyEx]}},
                           {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]),
    {Server, inet_port(Server)}.
 
cipher_flag('tlsv1.3') ->
     "-ciphersuites";
cipher_flag(_) ->
    "-cipher".

ciphers(Ciphers, Version) ->
    Strs = [ssl_cipher_format:suite_map_to_openssl_str(Cipher) || Cipher <- Ciphers],
    ciphers_concat(Version, Strs, "").

ciphers_concat(_, [], [":" | Acc]) ->
    lists:append(lists:reverse(Acc));
ciphers_concat('tlsv1.3' = Version, [Head| Tail], Acc) ->
    case Head of
        "TLS" ++ _ ->
            ciphers_concat(Version, Tail, [":", Head | Acc]);
        _ ->
            ciphers_concat(Version, Tail, Acc)
    end;
ciphers_concat(Version,  [Head| Tail], Acc) ->
    ciphers_concat(Version, Tail, [":", Head | Acc]).

start_server_with_raw_key(erlang, ServerOpts, Config) ->
    {_, ServerNode, _} = ssl_test_lib:run_where(Config),
    Server = start_server([{node, ServerNode}, {port, 0},
                           {from, self()},
                           {mfa, {ssl_test_lib,
                                  send_recv_result_active,
                                  []}},
                           {options,
                            [{verify, verify_peer} | ServerOpts]}]),
    {Server, inet_port(Server)}.

start_server_ecc(erlang, ServerOpts, Expect, ECCOpts, Config) ->
    {_, ServerNode, _} = run_where(Config),
    Server = start_server([{node, ServerNode}, {port, 0},
                                        {from, self()},
                                        {mfa, {?MODULE, check_ecc, [server, Expect]}},
                                        {options,
                                         ECCOpts ++
                                         [{verify, verify_peer} | ServerOpts]}]),
    {Server, inet_port(Server)}.

start_server_ecc_error(erlang, ServerOpts, ECCOpts, Config) ->
    {_, ServerNode, _} = run_where(Config),
    Server = start_server_error([{node, ServerNode}, {port, 0},
                                              {from, self()},
                                              {options,
                                               ECCOpts ++
                                               [{verify, verify_peer} | ServerOpts]}]),
    {Server, inet_port(Server)}.

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

stop(Port1, Port2) when is_port(Port1), is_port(Port2) ->
    close_port(Port1),
    close_port(Port2);
stop(Port, Pid) when is_port(Port) ->
    close_port(Port),
    close(Pid);
stop(Pid, Port) when is_port(Port) ->
    close_port(Port),
    close(Pid);
stop(Client, Server)  ->
    close(Server),
    close(Client).


openssl_cert_options(Opts, Role) ->
    Cert = proplists:get_value(certfile, Opts, undefined),
    Key = proplists:get_value(keyfile, Opts, undefined),
    CA = proplists:get_value(cacertfile, Opts, undefined),
    case CA of
        undefined ->
            case cert_option("-cert", Cert) ++ cert_option("-key", Key) of
                [] when Role == server ->
                    ["-nocert"];
                Other ->
                    Other
            end;
        _ ->
            cert_option("-cert", Cert) ++  cert_option("-CAfile", CA) ++
                cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"]
    end.

openssl_verify(Opts) ->
      case proplists:get_value(fail_if_no_peer_cert, Opts, undefined) of
          true ->
              ["-Verify"];
          _ ->
              ["-verify"]
      end.
    
cert_option(_, undefined) ->
    [];
cert_option(Opt, Value) ->
    [Opt, Value].

supported_eccs(Opts) ->
    ToCheck = proplists:get_value(eccs, Opts, []),
    Supported = ssl:eccs(),
    lists:all(fun(Curve) -> lists:member(Curve, Supported) end, ToCheck).

check_ecc(SSL, Role, Expect) ->
    {ok, Data} = ssl:connection_information(SSL),
    case lists:keyfind(ecc, 1, Data) of
        {ecc, {named_curve, Expect}} -> ok;
        Other -> {error, Role, Expect, Other}
    end.

inet_port(Pid) when is_pid(Pid)->
    receive
	{Pid, {port, Port}} ->
	    Port
    end;

inet_port(Node) ->
    {Port, Socket} = do_inet_port(Node),
     rpc:call(Node, gen_tcp, close, [Socket]),
     Port.

do_inet_port(Node) ->
    {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
    {ok, Port} = rpc:call(Node, inet, port, [Socket]),
    {Port, Socket}.

no_result(_) ->
    no_result_msg.

trigger_renegotiate(Socket, [ErlData, N]) ->
     {ok, [{session_id, Id}]} = ssl:connection_information(Socket,  [session_id]),
    trigger_renegotiate(Socket, ErlData, N, Id).

trigger_renegotiate(Socket, _, 0, Id) ->
    ct:sleep(1000),
    case ssl:connection_information(Socket,  [session_id]) of
        {ok, [{session_id, Id}]} ->
	    fail_session_not_renegotiated;
	%% Tests that uses this function will not reuse
	%% sessions so if we get a new session id the
	%% renegotiation has succeeded.
        {ok, [{session_id, _}]} -> 
	    ok;
	{error, closed} ->
	    fail_session_fatal_alert_during_renegotiation;
	{error, timeout} ->
	    fail_timeout
    end;

trigger_renegotiate(Socket, ErlData, N, Id) ->
    ssl:send(Socket, ErlData),
    trigger_renegotiate(Socket, ErlData, N-1, Id).				   
    

send_selected_port(Pid, 0, #sslsocket{} = Socket) ->
    {ok, {_, NewPort}} = ssl:sockname(Socket),	 
    Pid ! {self(), {port, NewPort}};
send_selected_port(Pid, 0, Socket) ->
    {ok, {_, NewPort}} = inet:sockname(Socket),	 
    Pid ! {self(), {port, NewPort}};
send_selected_port(_,_,_) ->
    ok.

rsa_suites(CounterPart) ->
    ECC = is_sane_ecc(CounterPart),
    FIPS = is_fips(CounterPart),
    CryptoSupport = crypto:supports(),
    Ciphers = proplists:get_value(ciphers, CryptoSupport),
    lists:filter(fun({rsa, des_cbc, sha}) when FIPS == true ->
			 false;
		    ({dhe_rsa, des_cbc, sha}) when FIPS == true ->
			 false;
		    ({rsa, Cipher, _}) ->
			 lists:member(cipher_atom(Cipher), Ciphers);
		    ({dhe_rsa, Cipher, _}) ->
			 lists:member(cipher_atom(Cipher), Ciphers);
		    ({ecdhe_rsa, Cipher, _}) when ECC == true ->
			 lists:member(cipher_atom(Cipher), Ciphers);
                    ({ecdhe_rsa, Cipher, _,_}) when ECC == true ->
			 lists:member(cipher_atom(Cipher), Ciphers);
		    ({rsa, Cipher, _, _}) ->
			 lists:member(cipher_atom(Cipher), Ciphers);
		    ({dhe_rsa, Cipher, _,_}) ->
			 lists:member(cipher_atom(Cipher), Ciphers);
                    (_) ->
			 false
		 end,
                 common_ciphers(CounterPart)).

common_ciphers(crypto) ->
    ssl:cipher_suites();
common_ciphers(openssl) ->
    OpenSslSuites =
        string:tokens(string:strip(os:cmd("openssl ciphers"), right, $\n), ":"),
    [ssl_cipher_format:suite_bin_to_map(S)
     || S <- ssl_cipher:suites(tls_record:highest_protocol_version([])),
        lists:member(ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(S)), OpenSslSuites)
    ].

available_suites(Version) ->
    [ssl_cipher_format:suite_bin_to_map(Suite) || 
	Suite  <-  ssl_cipher:filter_suites(ssl_cipher:suites(Version))].


rsa_non_signed_suites(Version) ->
    lists:filter(fun({rsa, _, _}) ->
			 false;
		    (_) ->
			 true
		 end,
		 available_suites(Version)).

dsa_suites(Version) ->
     lists:filter(fun({dhe_dss, _, _}) ->
			 true;
		    (_) ->
			 false
		 end,
		 available_suites(Version)).

ecdsa_suites(Version) ->
     lists:filter(fun({ecdhe_ecdsa, _, _}) ->
			 true;
		    (_) ->
			 false
		 end,
		 available_suites(Version)).

ecdh_rsa_suites(Version) ->
     lists:filter(fun({ecdh_rsa, _, _}) ->
			 true;
		    (_) ->
			 false
		 end,
		 available_suites(Version)).

openssl_rsa_suites() ->
    Ciphers = ssl:cipher_suites(openssl),
    lists:filter(fun(Str) -> string_regex_filter(Str, "RSA")
		 end, Ciphers) -- openssl_ecdh_rsa_suites().

openssl_dsa_suites() ->
    Ciphers = ssl:cipher_suites(openssl),
    lists:filter(fun(Str) -> string_regex_filter(Str, "DSS")
		 end, Ciphers).

openssl_ecdsa_suites() ->
    Ciphers = ssl:cipher_suites(openssl),
    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) -> string_regex_filter(Str, "ECDH-RSA")
		 end, Ciphers).

openssl_filter(FilterStr) ->
    Ciphers = string:tokens(os:cmd("openssl ciphers"), ":"),
    lists:filter(fun(Str) -> string_regex_filter(Str, FilterStr)
		 end, Ciphers).


string_regex_filter(Str, Search) when is_list(Str) ->
    case re:run(Str, Search, []) of
	nomatch ->
	    false;
	_ ->
	    true
    end;
string_regex_filter(_Str, _Search) ->
    false.

ecdh_dh_anonymous_suites(Version) ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:anonymous_suites(Version)],
                             [{key_exchange, 
                               fun(dh_anon) -> 
                                       true;
                                  (ecdh_anon) -> 
                                       true;
                                  (_) -> 
                                       false 
                               end}]).
psk_suites({3,_} = Version) ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites(Version)], []);
psk_suites(Version) ->
    ssl:filter_cipher_suites(psk_suites(dtls_v1:corresponding_tls_version(Version)), 
                             [{cipher, 
                               fun(rc4_128) -> 
                                       false;
                                  (_) -> 
                                       true 
                               end}]).

psk_anon_suites({3,_} = Version) ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites_anon(Version)], 
                             [{key_exchange, 
                               fun(psk) -> 
                                       true;
                                  (dhe_psk) -> 
                                       true;
                                  (ecdhe_psk) -> 
                                       true;
                                  (_) -> 
                                       false 
                               end}]);

psk_anon_suites(Version) ->
    ssl:filter_cipher_suites(psk_anon_suites(dtls_v1:corresponding_tls_version(Version)), 
                             [{cipher, 
                               fun(rc4_128) -> 
                                       false;
                                  (_) -> 
                                       true 
                               end}]).


srp_suites() ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()],
                             [{key_exchange, 
                               fun(srp_rsa) -> 
                                       true;
                                  (_) -> 
                                       false 
                               end}]).
srp_anon_suites() ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-  ssl_cipher:srp_suites_anon()],
                             []).
srp_dss_suites() ->
    ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()], 
                             [{key_exchange, 
                               fun(srp_dss) -> 
                                       true;
                                  (_) -> 
                                       false 
                               end}]).
chacha_suites(Version) ->
    [ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:filter_suites(ssl_cipher:chacha_suites(Version))].


rc4_suites(Version) ->
     ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:rc4_suites(Version)], []).

des_suites(Version) ->
     ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:des_suites(Version)], []).

tuple_to_map({Kex, Cipher, Mac}) ->
    #{key_exchange => Kex,
      cipher => Cipher,
      mac => Mac,
      prf => default_prf};
tuple_to_map({Kex, Cipher, Mac, Prf}) ->
    #{key_exchange => Kex,
      cipher => Cipher,
      mac => Mac,
      prf => Prf}.

pem_to_der(File) ->
    {ok, PemBin} = file:read_file(File),
    public_key:pem_decode(PemBin).

der_to_pem(File, Entries) ->
    PemBin = public_key:pem_encode(Entries),
    file:write_file(File, PemBin).

cipher_result(Socket, Result) ->
    {ok, Info} = ssl:connection_information(Socket),
    Result = {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_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
    Hello = "Hello\n",
    World = " world\n",
    ssl:send(Socket, Hello),
    ct:sleep(500),
    ssl:send(Socket, World),
    Expected = Hello ++ World,
    Expected = active_recv(Socket, length(Expected)),
    ok.

session_info_result(Socket) ->
    {ok, Info} = ssl:connection_information(Socket,  [session_id, cipher_suite]),
    Info.

public_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
				 #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?rsaEncryption},
			     privateKey = Key}) ->
    public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key));

public_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
				 #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'},
			     privateKey = Key}) ->
    public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
public_key(Key) ->
    Key.
receive_rizzo_duong_beast() ->
    receive 
	{ssl, _, "ello\n"} ->
	    receive 
		{ssl, _, " "} ->
		    receive
			{ssl, _, "world\n"} ->
			    ok
		    end
	    end
    end.


state([{data,[{"State", {_StateName, StateData}}]} | _]) -> %% gen_statem
    StateData;
state([{data,[{"State", State}]} | _]) -> %% gen_server
    State;
state([{data,[{"StateData", State}]} | _]) -> %% gen_fsm
     State;
state([_ | Rest]) ->
    state(Rest).

is_tls_version('dtlsv1.2') ->
    true;
is_tls_version('dtlsv1') ->
    true;
is_tls_version('tlsv1.3') ->
    true;
is_tls_version('tlsv1.2') ->
    true;
is_tls_version('tlsv1.1') ->
    true;
is_tls_version('tlsv1') ->
    true;
is_tls_version('sslv3') ->
    true;
is_tls_version(_) ->
    false.

init_tls_version(Version, Config)
  when Version == 'dtlsv1.2'; Version == 'dtlsv1' ->
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, dtls_protocol_version, Version),
    ssl:start(),
    NewConfig = proplists:delete(protocol_opts, proplists:delete(protocol, Config)),
    [{protocol, dtls}, {protocol_opts, [{protocol, dtls}]} | NewConfig];

init_tls_version(Version, Config) ->
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, protocol_version, Version),
    ssl:start(),
    NewConfig = proplists:delete(protocol_opts, proplists:delete(protocol, Config)),
    [{protocol, tls} | NewConfig].

clean_tls_version(Config) ->
    proplists:delete(protocol_opts, proplists:delete(protocol, Config)).

sufficient_crypto_support(Version)
  when Version == 'tlsv1.3' ->
    CryptoSupport = crypto:supports(),
    lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupport)) andalso
    lists:member(x448, proplists:get_value(curves, CryptoSupport));
sufficient_crypto_support(Version)
  when Version == 'tlsv1.2'; Version == 'dtlsv1.2' ->
    CryptoSupport = crypto:supports(),
    proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport));
sufficient_crypto_support(cipher_ec) -> 
    CryptoSupport = crypto:supports(),
    proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport));
sufficient_crypto_support(_) ->
    true.

check_key_exchange_send_active(Socket, false) ->
    send_recv_result_active(Socket);
check_key_exchange_send_active(Socket, KeyEx) ->
    {ok, Info} =
        ssl:connection_information(Socket, [cipher_suite, protocol]),
    Suite = proplists:get_value(cipher_suite, Info),
    Version = proplists:get_value(protocol, Info),
    true = check_key_exchange(Suite, KeyEx, Version), 
    send_recv_result_active(Socket).

check_key_exchange({KeyEx,_, _}, KeyEx, _) ->
    ct:pal("Kex: ~p", [KeyEx]),
    true;
check_key_exchange({KeyEx,_,_,_}, KeyEx, _) ->
    ct:pal("Kex: ~p", [KeyEx]),
    true;
check_key_exchange(KeyEx1, KeyEx2, Version) ->
    ct:pal("Kex: ~p ~p", [KeyEx1, KeyEx2]),
    case Version of
        'tlsv1.2' ->
            v_1_2_check(element(1, KeyEx1), KeyEx2);
        'dtlsv1.2' ->
            v_1_2_check(element(1, KeyEx1), KeyEx2);
        _ ->       
            ct:pal("Negotiated ~p  Expected ~p", [KeyEx1, KeyEx2]),
            false
    end.

v_1_2_check(ecdh_ecdsa, ecdh_rsa) ->
    true;
v_1_2_check(ecdh_rsa, ecdh_ecdsa) ->
    true;
v_1_2_check(_, _) ->
    false.

send_recv_result(Socket) ->
    Data =  "Hello world",
    ssl:send(Socket, Data),
    {ok, Data} = ssl:recv(Socket, length(Data)),
    ok.

send_recv_result_active(Socket) ->
    Data =  "Hello world",
    ssl:send(Socket, Data),
    Data = active_recv(Socket, length(Data)),
    ok.

send_recv_result_active_once(Socket) ->
    Data = "Hello world",
    ssl:send(Socket, Data),
    active_once_recv_list(Socket, length(Data)).

active_recv(Socket, N) ->
    active_recv(Socket, N, []).

active_recv(_Socket, 0, Acc) ->
    Acc;
active_recv(Socket, N, Acc) ->
    receive 
	{ssl, Socket, Bytes} ->
            active_recv(Socket, N-length(Bytes),  Acc ++ Bytes)
    end.

active_once_recv(_Socket, 0) ->
    ok;
active_once_recv(Socket, N) ->
    receive 
	{ssl, Socket, Bytes} ->
            ssl:setopts(Socket, [{active, once}]),
            active_once_recv(Socket, N-byte_size(Bytes))
    end.

active_once_recv_list(_Socket, 0) ->
    ok;
active_once_recv_list(Socket, N) ->
    receive 
	{ssl, Socket, Bytes} ->
            ssl:setopts(Socket, [{active, once}]),
            active_once_recv_list(Socket, N-length(Bytes))
    end.
recv_disregard(_Socket, 0) ->
    ok;
recv_disregard(Socket, N) ->
    {ok, Bytes} = ssl:recv(Socket, 0),
    recv_disregard(Socket, N-byte_size(Bytes)).

active_disregard(_Socket, 0) ->
    ok;
active_disregard(Socket, N) ->
    receive 
	{ssl, Socket, Bytes} ->
            active_disregard(Socket, N-byte_size(Bytes))
    end.
active_once_disregard(_Socket, 0) ->
    ok;
active_once_disregard(Socket, N) ->
    receive 
	{ssl, Socket, Bytes} ->
            ssl:setopts(Socket, [{active, once}]),
            active_once_disregard(Socket, N-byte_size(Bytes))
    end.

is_ipv6_supported() ->
    case os:cmd("openssl version") of
        "OpenSSL 0.9.8" ++ _ -> % Does not support IPv6
            false;
        "OpenSSL 1.0" ++ _ ->   % Does not support IPv6
            false;
        _ ->
            true
    end.

is_sane_ecc(openssl) ->
    case os:cmd("openssl version") of
	"OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl
	    %% manifests as SSL_CHECK_SERVERHELLO_TLSEXT:tls invalid ecpointformat list
	    false;
	"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
	    false;
	_ ->
	    true
    end;
is_sane_ecc(crypto) ->
    [{_,_, Bin}]  = crypto:info_lib(), 
    case binary_to_list(Bin) of
	"OpenSSL 0.9.8" ++ _ -> % Does not support ECC
	    false;
	"OpenSSL 0.9.7" ++ _ -> % Does not support ECC
	    false;
	_ ->
	    true
    end;
is_sane_ecc(_) ->
    sufficient_crypto_support(cipher_ec).

is_sane_oppenssl_client() ->
    [{_,_, Bin}]  = crypto:info_lib(), 
    case binary_to_list(Bin) of
	"OpenSSL 0.9" ++ _ -> 
	    false;
	_ ->
	    true
    end.

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) ->
    Version = protocol_version(Config0, tuple),
    case is_sane_ecc(openssl) of
	false ->
	    Opts = proplists:get_value(server_opts, Config0),
	    Config1 = proplists:delete(server_opts, Config0),
	    VerOpts = proplists:get_value(server_verification_opts, Config1),
	    Config = proplists:delete(server_verification_opts, Config1),
	    Restricted0 = ssl:cipher_suites() -- ecdsa_suites(Version),
            Restricted  = Restricted0 -- ecdh_rsa_suites(Version),
	    [{server_opts, [{ciphers, Restricted} | Opts]}, {server_verification_opts, [{ciphers, Restricted} | VerOpts] } | Config];
	true ->
	    Config0
    end.

openssl_dsa_support() ->
    case os:cmd("openssl version") of
        "LibreSSL 2.6.1" ++ _ ->
            true;
        "LibreSSL 2.6.2" ++ _ ->
            true;
        "LibreSSL 2.6" ++ _ ->
            false;
        "LibreSSL 2.4" ++ _ ->
            true;
        "LibreSSL 2.3" ++ _ ->
            true;
        "LibreSSL 2.2" ++ _ ->
            true;
        "LibreSSL 2.1" ++ _ ->
            true;
        "LibreSSL 2.0" ++ _ ->
            true;
        "LibreSSL"  ++ _ ->
            false;
        "OpenSSL 1.1" ++ _Rest ->
            false;
        "OpenSSL 1.0.1" ++ Rest ->
            hd(Rest) >= $s;
        _ ->
            true
    end.

%% Acctual support is tested elsewhere, this is to exclude some LibreSSL and OpenSSL versions
openssl_sane_dtls() -> 
    case os:cmd("openssl version") of
        "OpenSSL 0." ++ _ ->
            false;
        "OpenSSL 1.0.1s-freebsd" ++ _ ->
            false;
        "OpenSSL 1.0.2k-freebsd" ++ _ ->
            false;
        "OpenSSL 1.0.2" ++ _ ->
            false;
        "OpenSSL 1.0.0" ++ _ ->
            false;
        "OpenSSL" ++ _ ->
            true;
        "LibreSSL 2.7" ++ _ ->
            true;
        _ ->
            false
        end.
openssl_sane_client_cert() -> 
    case os:cmd("openssl version") of
        "LibreSSL 2.5.2" ++ _ ->
            true;
        "LibreSSL 2.4" ++ _ ->
            false;
        "LibreSSL 2.3" ++ _ ->
            false; 
         "LibreSSL 2.1" ++ _ ->
            false; 
         "LibreSSL 2.0" ++ _ ->
            false; 
        "OpenSSL 1.0.1s-freebsd" ->
            false;
        "OpenSSL 1.0.0" ++ _ ->
            false; 
        _ ->
            true
    end.

check_sane_openssl_version(Version) ->
    case supports_ssl_tls_version(Version) of 
	true ->
	    case {Version, os:cmd("openssl version")} of
                {'sslv3', "OpenSSL 1.0.2" ++ _} ->
                    false;
                {'dtlsv1', "OpenSSL 0" ++ _} ->
		    false;
		{'dtlsv1.2', "OpenSSL 0" ++ _} ->
		    false;
                {'dtlsv1.2', "OpenSSL 1.0.2" ++ _} ->
		    false;
		{'dtlsv1',  "OpenSSL 1.0.0" ++ _} ->
		    false;
                {'dtlsv1', _} ->
		    not is_fips(openssl);
		{'dtlsv1.2', _} ->
		    not is_fips(openssl);
		{_, "OpenSSL 1.0.2" ++ _} ->
		    true;
		{_, "OpenSSL 1.0.1" ++ _} ->
		    true;
		{'tlsv1.2', "OpenSSL 1.0.0" ++ _} ->
		    false;
		{'tlsv1.1', "OpenSSL 1.0.0" ++ _} ->
		    false;
		{'tlsv1.2', "OpenSSL 0" ++ _} ->
		    false;
		{'tlsv1.1', "OpenSSL 0" ++ _} ->
		    false;
                {'tlsv1', "OpenSSL 0" ++ _} ->
		    false;
		{_, _} ->
		    true
	    end;
	false ->
	    false
    end.
check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1';
						     Version == 'tlsv1.2' ->
    case os:cmd("openssl version") of     
	"OpenSSL 1.0.1c" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	"OpenSSL 1.0.1b" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	"OpenSSL 1.0.1a" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	"OpenSSL 1.0.1 " ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	_ ->
	    check_sane_openssl_renegotaite(Config)
    end;
check_sane_openssl_renegotaite(Config, 'sslv3') ->
    case os:cmd("openssl version") of     
	"OpenSSL 1" ++ _ ->
	    {skip, "Known renegotiation bug with sslv3 in OpenSSL"};
	_ ->
	    check_sane_openssl_renegotaite(Config)
    end;
check_sane_openssl_renegotaite(Config, _) ->
    check_sane_openssl_renegotaite(Config).
	
check_sane_openssl_renegotaite(Config) ->
    case os:cmd("openssl version") of  
	"OpenSSL 1.0.0" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	"OpenSSL 0.9.8" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	"OpenSSL 0.9.7" ++ _ ->
	    {skip, "Known renegotiation bug in OpenSSL"};
	_ ->
	    Config
    end.

workaround_openssl_s_clinent() ->
    %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159
    %% https://bugs.archlinux.org/task/33919
    %% Bug seems to manifests it self if TLS version is not
    %% explicitly specified 
    case os:cmd("openssl version") of 
	"OpenSSL 1.0.1c" ++ _ ->
	    ["-no_tls1_2"];
	"OpenSSL 1.0.1d" ++ _ ->
	    ["-no_tls1_2"];
	"OpenSSL 1.0.1e" ++ _ ->
	    ["-no_tls1_2"];
	"OpenSSL 1.0.1f" ++ _ ->
	    ["-no_tls1_2"];
	_  ->
	    []
    end.

enough_openssl_crl_support("OpenSSL 0." ++ _) -> false;
enough_openssl_crl_support(_) -> true.

wait_for_openssl_server(Port, tls) ->
    do_wait_for_openssl_tls_server(Port, 10);
wait_for_openssl_server(_Port, dtls) ->
    ok. %% No need to wait for DTLS over UDP server
        %% client will retransmitt until it is up.

do_wait_for_openssl_tls_server(_, 0) ->
    exit(failed_to_connect_to_openssl);
do_wait_for_openssl_tls_server(Port, N) ->
    case gen_tcp:connect("localhost", Port, []) of
	{ok, S} ->
	    gen_tcp:close(S);
	_  ->
	    ct:sleep(?SLEEP),
	    do_wait_for_openssl_tls_server(Port, N-1)
    end.

version_flag(tlsv1) ->
    "-tls1";
version_flag('tlsv1.1') ->
    "-tls1_1";
version_flag('tlsv1.2') ->
    "-tls1_2";
version_flag('tlsv1.3') ->
    "-tls1_3";
version_flag(sslv3) ->
    "-ssl3";
version_flag(sslv2) ->
    "-ssl2";
version_flag('dtlsv1.2') ->
    "-dtls1_2";
version_flag('dtlsv1') ->
    "-dtls1".

filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_list(Cipher)->
    filter_suites([ssl_cipher_format:suite_openssl_str_to_map(S) || S <- Ciphers], 
                  AtomVersion);
filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_binary(Cipher)->
    filter_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- Ciphers], 
                  AtomVersion);
filter_suites(Ciphers0, AtomVersion) ->
    Version = tls_version(AtomVersion),
    Supported0 = ssl_cipher:suites(Version)
	++ ssl_cipher:anonymous_suites(Version)
	++ ssl_cipher:psk_suites(Version)
        ++ ssl_cipher:psk_suites_anon(Version)
	++ ssl_cipher:srp_suites(Version) 
        ++ ssl_cipher:srp_suites_anon(Version) 
	++ ssl_cipher:rc4_suites(Version),
    Supported1 = ssl_cipher:filter_suites(Supported0),
    Supported2 = [ssl_cipher_format:suite_bin_to_map(S) || S <- Supported1],
    [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)].

-define(OPENSSL_QUIT, "Q\n").
close_port(Port) ->
    catch port_command(Port, ?OPENSSL_QUIT),
    close_loop(Port, 500, false).

close_loop(Port, Time, SentClose) ->
    receive 
	{Port, {data,Debug}} when is_port(Port) ->
	    ct:log("openssl ~s~n",[Debug]),
	    close_loop(Port, Time, SentClose);	
	{ssl,_,Msg} ->
	    ct:log("ssl Msg ~s~n",[Msg]),
	    close_loop(Port, Time, SentClose);	
	{Port, closed} -> 
	    ct:log("Port Closed~n",[]),
	    ok;
	{'EXIT', Port, Reason} ->
	    ct:log("Port Closed ~p~n",[Reason]),
	    ok;
	Msg ->
	    ct:log("Port Msg ~p~n",[Msg]),
	    close_loop(Port, Time, SentClose)
    after Time ->
	    case SentClose of
		false -> 
		    ct:log("Closing port ~n",[]),
		    catch erlang:port_close(Port),
		    close_loop(Port, Time, true);
		true ->
		    ct:log("Timeout~n",[])
	    end
    end.

portable_open_port(Exe, Args) ->
    AbsPath = os:find_executable(Exe),
    ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
    open_port({spawn_executable, AbsPath}, 
	      [{args, Args}, stderr_to_stdout]). 

supports_ssl_tls_version(sslv2 = Version) ->
    case os:cmd("openssl version") of
	"OpenSSL 1" ++ _ -> 
	    false;
        %% Appears to be broken
        "OpenSSL 0.9.8.o" ++ _ -> 
            false;
	_ ->
            VersionFlag = version_flag(Version),
            Exe = "openssl",
            Args = ["s_client", VersionFlag],
            [{trap_exit, Trap}] = process_info(self(), [trap_exit]),
            process_flag(trap_exit, true),
            Port = ssl_test_lib:portable_open_port(Exe, Args),
            Bool = do_supports_ssl_tls_version(Port, ""),
            consume_port_exit(Port),
            process_flag(trap_exit, Trap),
            Bool
    end;

supports_ssl_tls_version(Version) ->
    VersionFlag = version_flag(Version),
    Exe = "openssl",
    Args = ["s_client", VersionFlag],
    Port = ssl_test_lib:portable_open_port(Exe, Args),
    do_supports_ssl_tls_version(Port, "").

do_supports_ssl_tls_version(Port, Acc) ->
    receive 
        {Port, {data, Data}} -> 
            case Acc ++ Data of
                "unknown option"  ++ _ ->
                    false;
                "s_client: Option unknown" ++ _->
                    false;
                Info when length(Info) >= 24 ->
                    ct:pal("~p", [Info]),
                    true;
                _ ->
                    do_supports_ssl_tls_version(Port, Acc ++ Data)
            end
    after 1000 ->
            true                        
    end.

ssl_options(Option, Config) when is_atom(Option) ->
    ProtocolOpts = proplists:get_value(protocol_opts, Config, []),
    Opts = proplists:get_value(Option, Config, []),
    Opts ++ ProtocolOpts;
ssl_options(Options, Config) ->
    ProtocolOpts = proplists:get_value(protocol_opts, Config, []),
    Options ++ ProtocolOpts.

protocol_version(Config) ->
   protocol_version(Config, atom).

protocol_version(Config, tuple) ->
    case proplists:get_value(protocol, Config) of
	dtls ->
	    dtls_record:highest_protocol_version(dtls_record:supported_protocol_versions());
	_ ->
	    tls_record:highest_protocol_version(tls_record:supported_protocol_versions())
   end;

protocol_version(Config, atom) ->
    case proplists:get_value(protocol, Config) of
	dtls ->
	   dtls_record:protocol_version(protocol_version(Config, tuple));
	_ ->							 
           tls_record:protocol_version(protocol_version(Config, tuple))	
   end.

protocol_versions(Config) ->
    Version = protocol_version(Config),
    case Version of
        'tlsv1.3' -> %% TLS-1.3 servers shall also support 1.2
            ['tlsv1.3', 'tlsv1.2'];
        _ ->
            [Version]
    end.
protocol_options(Config, Options) ->
    Protocol = proplists:get_value(protocol, Config, tls),
    {Protocol, Opts} = lists:keyfind(Protocol, 1, Options),
    Opts.

ct_log_supported_protocol_versions(Config) ->
    case proplists:get_value(protocol, Config) of
	dtls ->
	    ct:log("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]);
	_ ->
	    ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()])
    end.

clean_env() ->
    application:unset_env(ssl, protocol_version),
    application:unset_env(ssl, dtls_protocol_version),
    application:unset_env(ssl, session_lifetime),
    application:unset_env(ssl, session_cb),
    application:unset_env(ssl, session_cb_init_args),
    application:unset_env(ssl, session_cache_client_max),
    application:unset_env(ssl, session_cache_server_max),
    application:unset_env(ssl, ssl_pem_cache_clean),
    application:unset_env(ssl, bypass_pem_cache),
    application:unset_env(ssl, alert_timeout),
    application:unset_env(ssl, internal_active_n).

clean_start() ->
    ssl:stop(),
    application:load(ssl),
    clean_env(),
    ssl:start().

is_psk_anon_suite({psk, _,_}) ->
    true;
is_psk_anon_suite({dhe_psk,_,_}) ->
    true;
is_psk_anon_suite({ecdhe_psk,_,_}) ->
    true;
is_psk_anon_suite({psk, _,_,_}) ->
    true;
is_psk_anon_suite({dhe_psk, _,_,_}) ->
    true;
is_psk_anon_suite({ecdhe_psk, _,_,_}) ->
    true;
is_psk_anon_suite(_) ->
    false.

cipher_atom(aes_256_cbc) ->
    aes_cbc256;
cipher_atom(aes_128_cbc) ->
    aes_cbc128;
cipher_atom('3des_ede_cbc') ->
    des_ede3;
cipher_atom(Atom) ->
    Atom.
tls_version('dtlsv1' = Atom) ->
    dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
tls_version('dtlsv1.2' = Atom) ->
    dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
tls_version(Atom) ->
    tls_record:protocol_version(Atom).

consume_port_exit(OpenSSLPort) ->
    receive    	
        {'EXIT', OpenSSLPort, _} ->
            ok
    end.

hardcode_rsa_key(1) ->
    #'RSAPrivateKey'{
       version = 'two-prime',
       modulus = 23995666614853919027835084074500048897452890537492185072956789802729257783422306095699263934587064480357348855732149402060270996295002843755712064937715826848741191927820899197493902093529581182351132392364214171173881547273475904587683433713767834856230531387991145055273426806331200574039205571401702219159773947658558490957010003143162250693492642996408861265758000254664396313741422909188635443907373976005987612936763564996605457102336549804831742940035613780926178523017685712710473543251580072875247250504243621640157403744718833162626193206685233710319205099867303242759099560438381385658382486042995679707669,
       publicExponent = 17,
       privateExponent = 11292078406990079542510627799764728892919007311761028269626724613049062486316379339152594792746853873109340637991599718616598115903530750002688030558925094987642913848386305504703012749896273497577003478759630198199473669305165131570674557041773098755873191241407597673069847908861741446606684974777271632545629600685952292605647052193819136445675100211504432575554351515262198132231537860917084269870590492135731720141577986787033006338680118008484613510063003323516659048210893001173583018220214626635609151105287049126443102976056146630518124476470236027123782297108342869049542023328584384300970694412006494684657,
       prime1 = 169371138592582642967021557955633494538845517070305333860805485424261447791289944610138334410987654265476540480228705481960508520379619587635662291973699651583489223555422528867090299996446070521801757353675026048850480903160224210802452555900007597342687137394192939372218903554801584969667104937092080815197,
       prime2 = 141675062317286527042995673340952251894209529891636708844197799307963834958115010129693036021381525952081167155681637592199810112261679449166276939178032066869788822014115556349519329537177920752776047051833616197615329017439297361972726138285974555338480581117881706656603857310337984049152655480389797687577,
       exponent1 = 119556097830058336212015217380447172615655659108450823901745048534772786676204666783627059584226579481512852103690850928442711896738555003036938088452023283470698275450886490965004917644550167427154181661417665446247398284583687678213495921811770068712485038160606780733330990744565824684470897602653233516609,
       exponent2 = 41669135975672507953822256864985956439473391144599032012999352737636422046504414744027363535700448809435637398729893409470532385959317485048904982111185902020526124121798693043976273393287623750816484427009887116945685005129205106462566511260580751570141347387612266663707016855981760014456663376585234613993,
       coefficient = 76837684977089699359024365285678488693966186052769523357232308621548155587515525857011429902602352279058920284048929101483304120686557782043616693940283344235057989514310975192908256494992960578961614059245280827077951132083993754797053182279229469590276271658395444955906108899267024101096069475145863928441,
       otherPrimeInfos = asn1_NOVALUE};

hardcode_rsa_key(2) ->
    #'RSAPrivateKey'{
       version = 'two-prime',
       modulus = 21343679768589700771839799834197557895311746244621307033143551583788179817796325695589283169969489517156931770973490560582341832744966317712674900833543896521418422508485833901274928542544381247956820115082240721897193055368570146764204557110415281995205343662628196075590438954399631753508888358737971039058298703003743872818150364935790613286541190842600031570570099801682794056444451081563070538409720109449780410837763602317050353477918147758267825417201591905091231778937606362076129350476690460157227101296599527319242747999737801698427160817755293383890373574621116766934110792127739174475029121017282777887777,
       publicExponent = 17,
       privateExponent = 18832658619343853622211588088997845201745658451136447382185486691577805721584993260814073385267196632785528033211903435807948675951440868570007265441362261636545666919252206383477878125774454042314841278013741813438699754736973658909592256273895837054592950290554290654932740253882028017801960316533503857992358685308186680144968293076156011747178275038098868263178095174694099811498968993700538293188879611375604635940554394589807673542938082281934965292051746326331046224291377703201248790910007232374006151098976879987912446997911775904329728563222485791845480864283470332826504617837402078265424772379987120023773,
       prime1 = 146807662748886761089048448970170315054939768171908279335181627815919052012991509112344782731265837727551849787333310044397991034789843793140419387740928103541736452627413492093463231242466386868459637115999163097726153692593711599245170083315894262154838974616739452594203727376460632750934355508361223110419,
       prime2 = 145385325050081892763917667176962991350872697916072592966410309213561884732628046256782356731057378829876640317801978404203665761131810712267778698468684631707642938779964806354584156202882543264893826268426566901882487709510744074274965029453915224310656287149777603803201831202222853023280023478269485417083,
       exponent1 = 51814469205489445090252393754177758254684624060673510353593515699736136004585238510239335081623236845018299924941168250963996835808180162284853901555621683602965806809675350150634081614988136541809283687999704622726877773856604093851236499993845033701707873394143336209718962603456693912094478414715725803677,
       exponent2 = 51312467664734785681382706062457526359131540440966797517556579722433606376221663384746714140373192528191755406283051201483646739222992016094510128871300458249756331334105225772206172777487956446433115153562317730076172132768497908567634716277852432109643395464627389577600646306666889302334125933506877206029,
       coefficient = 30504662229874176232343608562807118278893368758027179776313787938167236952567905398252901545019583024374163153775359371298239336609182249464886717948407152570850677549297935773605431024166978281486607154204888016179709037883348099374995148481968169438302456074511782717758301581202874062062542434218011141540,
       otherPrimeInfos = asn1_NOVALUE};
hardcode_rsa_key(3) -> 
    #'RSAPrivateKey'{ 
       version = 'two-prime',
       modulus = 25089040456112869869472694987833070928503703615633809313972554887193090845137746668197820419383804666271752525807484521370419854590682661809972833718476098189250708650325307850184923546875260207894844301992963978994451844985784504212035958130279304082438876764367292331581532569155681984449177635856426023931875082020262146075451989132180409962870105455517050416234175675478291534563995772675388370042873175344937421148321291640477650173765084699931690748536036544188863178325887393475703801759010864779559318631816411493486934507417755306337476945299570726975433250753415110141783026008347194577506976486290259135429,
       publicExponent = 17,
       privateExponent = 8854955455098659953931539407470495621824836570223697404931489960185796768872145882893348383311931058684147950284994536954265831032005645344696294253579799360912014817761873358888796545955974191021709753644575521998041827642041589721895044045980930852625485916835514940558187965584358347452650930302268008446431977397918214293502821599497633970075862760001650736520566952260001423171553461362588848929781360590057040212831994258783694027013289053834376791974167294527043946669963760259975273650548116897900664646809242902841107022557239712438496384819445301703021164043324282687280801738470244471443835900160721870265,
       prime1 = 171641816401041100605063917111691927706183918906535463031548413586331728772311589438043965564336865070070922328258143588739626712299625805650832695450270566547004154065267940032684307994238248203186986569945677705100224518137694769557564475390859269797990555863306972197736879644001860925483629009305104925823,
       prime2 =146170909759497809922264016492088453282310383272504533061020897155289106805616042710009332510822455269704884883705830985184223718261139908416790475825625309815234508695722132706422885088219618698987115562577878897003573425367881351537506046253616435685549396767356003663417208105346307649599145759863108910523,
       exponent1 = 60579464612132153154728441333538327425711971378777222246428851853999433684345266860486105493295364142377972586444050678378691780811632637288529186629507258781295583787741625893888579292084087601124818789392592131211843947578009918667375697196773859928702549128225990187436545756706539150170692591519448797349,
       exponent2 = 137572620950115585809189662580789132500998007785886619351549079675566218169991569609420548245479957900898715184664311515467504676010484619686391036071176762179044243478326713135456833024206699951987873470661533079532774988581535389682358631768109586527575902839864474036157372334443583670210960715165278974609,
       coefficient = 15068630434698373319269196003209754243798959461311186548759287649485250508074064775263867418602372588394608558985183294561315208336731894947137343239541687540387209051236354318837334154993136528453613256169847839789803932725339395739618592522865156272771578671216082079933457043120923342632744996962853951612,
       otherPrimeInfos = asn1_NOVALUE};
hardcode_rsa_key(4) -> 
    #'RSAPrivateKey'{
       version ='two-prime',
       modulus = 28617237755030755643854803617273584643843067580642149032833640135949799721163782522787597288521902619948688786051081993247908700824196122780349730169173433743054172191054872553484065655968335396052034378669869864779940355219732200954630251223541048434478476115391643898092650304645086338265930608997389611376417609043761464100338332976874588396803891301015812818307951159858145399281035705713082131199940309445719678087542976246147777388465712394062188801177717719764254900022006288880246925156931391594131839991579403409541227225173269459173129377291869028712271737734702830877034334838181789916127814298794576266389,
       publicExponent = 17,
       privateExponent = 26933870828264240605980991639786903194205240075898493207372837775011576208154148256741268036255908348187001210401018346586267012540419880263858569570986761169933338532757527109161473558558433313931326474042230460969355628442100895016122589386862163232450330461545076609969553227901257730132640573174013751883368376011370428995523268034111482031427024082719896108094847702954695363285832195666458915142143884210891427766607838346722974883433132513540317964796373298134261669479023445911856492129270184781873446960437310543998533283339488055776892320162032014809906169940882070478200435536171854883284366514852906334641,
       prime1 = 177342190816702392178883147766999616783253285436834252111702533617098994535049411784501174309695427674025956656849179054202187436663487378682303508229883753383891163725167367039879190685255046547908384208614573353917213168937832054054779266431207529839577747601879940934691505396807977946728204814969824442867,
       prime2 = 161367340863680900415977542864139121629424927689088951345472941851682581254789586032968359551717004797621579428672968948552429138154521719743297455351687337112710712475376510559020211584326773715482918387500187602625572442687231345855402020688502483137168684570635690059254866684191216155909970061793538842967,
       exponent1 = 62591361464718491357252875682470452982324688977706206627659717747211409835899792394529826226951327414362102349476180842659595565881230839534930649963488383547255704844176717778780890830090016428673547367746320007264898765507470136725216211681602657590439205035957626212244060728285168687080542875871702744541,
       exponent2 = 28476589564178982426348978152495139111074987239250991413906989738532220221433456358759122273832412611344984605059935696803369847909621479954699550944415412431654831613301737157474154985469430655673456186029444871051571607533040825739188591886206320553618003159523945304574388238386685203984112363845918619347,
       coefficient = 34340318160575773065401929915821192439103777558577109939078671096408836197675640654693301707202885840826672396546056002756167635035389371579540325327619480512374920136684787633921441576901246290213545161954865184290700344352088099063404416346968182170720521708773285279884132629954461545103181082503707725012,
       otherPrimeInfos = asn1_NOVALUE};

hardcode_rsa_key(5) -> 
    #'RSAPrivateKey'{ 
       version= 'two-prime',
       modulus = 26363170152814518327068346871197765236382539835597898797762992537312221863402655353436079974302838986536256364057947538018476963115004626096654613827403121905035011992899481598437933532388248462251770039307078647864188314916665766359828262009578648593031111569685489178543405615478739906285223620987558499488359880003693226535420421293716164794046859453204135383236667988765227190694994861629971618548127529849059769249520775574008363789050621665120207265361610436965088511042779948238320901918522125988916609088415989475825860046571847719492980547438560049874493788767083330042728150253120940100665370844282489982633,
       publicExponent = 17,
       privateExponent = 10855423004100095781734025182257903332628104638187370093196526338893267826106975733767797636477639582691399679317978398007608161282648963686857782164224814902073240232370374775827384395689278778574258251479385325591136364965685903795223402003944149420659869469870495544106108194608892902588033255700759382142132115013969680562678811046675523365751498355532768935784747314021422035957153013494814430893022253205880275287307995039363642554998244274484818208792520243113824379110193356010059999642946040953102866271737127640405568982049887176990990501963784502429481034227543991366980671390566584211881030995602076468001,
       prime1 =163564135568104310461344551909369650951960301778977149705601170951529791054750122905880591964737953456660497440730575925978769763154927541340839715938951226089095007207042122512586007411328664679011914120351043948122025612160733403945093961374276707993674792189646478659304624413958625254578122842556295400709,
       prime2 = 161179405627326572739107057023381254841260287988433675196680483761672455172873134522398837271764104320975746111042211695289319249471386600030523328069395763313848583139553961129874895374324504709512019736703349829576024049432816885712623938437949550266365056310544300920756181033500610331519029869549723159637,
       exponent1 = 115457036871603042678596154288966812436677860079277988027483179495197499568058910286503947269226790675289762899339230065396778656344654735064122152427494983121714122734382674714766593466820233891067233496718383963380253373289929461608301619793607087995535147427985749641862087821617853120878674947686796753441,
       exponent2 = 142217122612346975946270932667689342506994371754500301644129838613240401623123353990351915239791856753802128921507833848784693455415929352968108818884760967629866396887841730408713142977345151214275311532385308673155315337734838428569962298621720191411498579097539089047726042088382891468987379296661520434973,
       coefficient = 40624877259097915043489529504071755460170951428490878553842519165800720914888257733191322215286203357356050737713125202129282154441426952501134581314792133018830748896123382106683994268028624341502298766844710276939303555637478596035491641473828661569958212421472263269629366559343208764012473880251174832392,
       otherPrimeInfos = asn1_NOVALUE};
hardcode_rsa_key(6) -> 
    #'RSAPrivateKey'{ 
       version = 'two-prime',
       modulus = 22748888494866396715768692484866595111939200209856056370972713870125588774286266397044592487895293134537316190976192161177144143633669641697309689280475257429554879273045671863645233402796222694405634510241820106743648116753479926387434021380537483429927516962909367257212902212159798399531316965145618774905828756510318897899298783143203190245236381440043169622358239226123652592179006905016804587837199618842875361941208299410035232803124113612082221121192550063791073372276763648926636149384299189072950588522522800393261949880796214514243704858378436010975184294077063518776479282353562934591448646412389762167039,
       publicExponent = 17,
       privateExponent = 6690849557313646092873144848490175032923294179369428344403739373566349639495960705013115437616262686628622409110644753287395336362844012263914614494257428655751435080307550548130951000822418439531068973600535325512837681398082331290421770994275730420566916753796872722709677121223470117509210872101652580854566448661533030419787125312956120661097410038933324613372774190658239039998357548275441758790939430824924502690997433186652165055694361752689819209062683281242276039100201318203707142383491769671330743466041394101421674581185260900666085723130684175548215193875544802254923825103844262661010117443222587769713,
       prime1 = 164748737139489923768181260808494855987398781964531448608652166632780898215212977127034263859971474195908846263894581556691971503119888726148555271179103885786024920582830105413607436718060544856016793981261118694063993837665813285582095833772675610567592660039821387740255651489996976698808018635344299728063,
       prime2 = 138082323967104548254375818343885141517788525705334488282154811252858957969378263753268344088034079842223206527922445018725900110643394926788280539200323021781309918753249061620424428562366627334409266756720941754364262467100514166396917565961434203543659974860389803369482625510495464845206228470088664021953,
       exponent1 = 19382204369351755737433089506881747763223386113474288071606137250915399790025056132592266336467232258342217207517009594904937823896457497193947678962247515974826461245038835931012639613889475865413740468383661022831058098548919210068481862796785365949128548239978986792971253116470232552800943368864035262125,
       exponent2 = 48734937870742781736838524121371226418043009072470995864289933383361985165662916618800592031070851709019955245149098241903258862580021738866451955011878713569874088971734962924855680669070574353320917678842685325069739694270769705787147376221682660074232932303666989424523279591939575827719845342384234360689,
       coefficient = 81173034184183681160439870161505779100040258708276674532866007896310418779840630960490793104541748007902477778658270784073595697910785917474138815202903114440800310078464142273778315781957021015333260021813037604142367434117205299831740956310682461174553260184078272196958146289378701001596552915990080834227,
       otherPrimeInfos = asn1_NOVALUE}.

hardcode_dsa_key(1) -> 
    {'DSAPrivateKey',0,
     99438313664986922963487511141216248076486724382260996073922424025828494981416579966171753999204426907349400798052572573634137057487829150578821328280864500098312146772602202702021153757550650696224643730869835650674962433068943942837519621267815961566259265204876799778977478160416743037274938277357237615491,
     1454908511695148818053325447108751926908854531909,
     20302424198893709525243209250470907105157816851043773596964076323184805650258390738340248469444700378962907756890306095615785481696522324901068493502141775433048117442554163252381401915027666416630898618301033737438756165023568220631119672502120011809327566543827706483229480417066316015458225612363927682579,
     48598545580251057979126570873881530215432219542526130654707948736559463436274835406081281466091739849794036308281564299754438126857606949027748889019480936572605967021944405048011118039171039273602705998112739400664375208228641666852589396502386172780433510070337359132965412405544709871654840859752776060358,
     1457508827177594730669011716588605181448418352823};
hardcode_dsa_key(2) -> 
    #'DSAPrivateKey'{
       version = 0,
       p = 145447354557382582722944332987784622105075065624518040072393858097520305927329240484963764783346271194321683798321743658303478090647837211867389721684646254999291098347011037298359107547264573476540026676832159205689428125157386525591130716464335426605521884822982379206842523670736739023467072341958074788151,
       q = 742801637799670234315651916144768554943688916729,
       g = 79727684678125120155622004643594683941478642656111969487719464672433839064387954070113655822700268007902716505761008423792735229036965034283173483862273639257533568978482104785033927768441235063983341565088899599358397638308472931049309161811156189887217888328371767967629005149630676763492409067382020352505,
       y = 35853727034965131665219275925554159789667905059030049940938124723126925435403746979702929280654735557166864135215989313820464108440192507913554896358611966877432546584986661291483639036057475682547385322659469460385785257933737832719745145778223672383438466035853830832837226950912832515496378486927322864228,
       x = 801315110178350279541885862867982846569980443911};
hardcode_dsa_key(3) -> 
    #'DSAPrivateKey'{
       version = 0,
       p =  99438313664986922963487511141216248076486724382260996073922424025828494981416579966171753999204426907349400798052572573634137057487829150578821328280864500098312146772602202702021153757550650696224643730869835650674962433068943942837519621267815961566259265204876799778977478160416743037274938277357237615491,
       q =  1454908511695148818053325447108751926908854531909,
       g =  20302424198893709525243209250470907105157816851043773596964076323184805650258390738340248469444700378962907756890306095615785481696522324901068493502141775433048117442554163252381401915027666416630898618301033737438756165023568220631119672502120011809327566543827706483229480417066316015458225612363927682579,
       y =  48598545580251057979126570873881530215432219542526130654707948736559463436274835406081281466091739849794036308281564299754438126857606949027748889019480936572605967021944405048011118039171039273602705998112739400664375208228641666852589396502386172780433510070337359132965412405544709871654840859752776060358,
       x = 1457508827177594730669011716588605181448418352823}.

tcp_delivery_workaround(Server, ServerMsg, Client, ClientMsg) ->
    receive
	{Server, ServerMsg} ->
	    client_msg(Client, ClientMsg);
	{Client, ClientMsg} ->
	    server_msg(Server, ServerMsg);
	{Client, {error,closed}} ->
	    server_msg(Server, ServerMsg);
	{Server, {error,closed}} ->
	    client_msg(Client, ClientMsg)
    end.
client_msg(Client, ClientMsg) ->
    receive
	{Client, ClientMsg} ->
	    ok;
	{Client, {error,closed}} ->
	    ct:log("client got close"),
	    ok;
	{Client, {error, Reason}} ->
	    ct:log("client got econnaborted: ~p", [Reason]),
	    ok;
	Unexpected ->
	    ct:fail(Unexpected)
    end.
server_msg(Server, ServerMsg) ->
    receive
	{Server, ServerMsg} ->
	    ok;
	{Server, {error,closed}} ->
	    ct:log("server got close"),
	    ok;
	{Server, {error, Reason}} ->
	    ct:log("server got econnaborted: ~p", [Reason]),
	    ok;
	Unexpected ->
	    ct:fail(Unexpected)
    end.

session_id(Socket) ->
    {ok, [{session_id, ID}]} = ssl:connection_information(Socket, [session_id]),
    ID.
    
reuse_session(ClientOpts, ServerOpts, Config) ->
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    
    Server0 =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {tcp_options, [{active, false}]},
				   {options, ServerOpts}]),
    Port0 = ssl_test_lib:inet_port(Server0),
    
    Client0 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port0}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, [{reuse_sessions, save} | ClientOpts]}]),
    Server0 ! listen,
    
    Client1 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port0}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, ClientOpts}]),    

    SID = receive
              {Client0, Id0} ->
                  Id0
          end,
       
    receive
        {Client1, SID} ->
            ok
    after ?SLEEP ->
              ct:fail(session_not_reused)
    end,
  
    Server0 ! listen,
    
    Client2 =
        ssl_test_lib:start_client([{node, ClientNode},
                                   {port, Port0}, {host, Hostname},
                                   {mfa, {ssl_test_lib, session_id, []}},
                                   {from, self()},  {options, [{reuse_sessions, false}
         					  | ClientOpts]}]),   
    receive
         {Client2, SID} ->
            ct:fail(session_reused_when_session_reuse_disabled_by_client);
         {Client2, _} ->
            ok
    end,
    
    ssl_test_lib:close(Server0),
    ssl_test_lib:close(Client0),
    ssl_test_lib:close(Client1),
    ssl_test_lib:close(Client2),
    
    Server1 =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {tcp_options, [{active, false}]},
				   {options, [{reuse_sessions, false} |ServerOpts]}]),
    Port1 = ssl_test_lib:inet_port(Server1),
    
    Client3 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port1}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, [{reuse_sessions, save} | ClientOpts]}]),
    SID1 = receive
               {Client3, Id3} ->
                   Id3
           end,

    Server1 ! listen,
    
    Client4 =
        ssl_test_lib:start_client([{node, ClientNode},
                                   {port, Port1}, {host, Hostname},
                                   {mfa, {ssl_test_lib, session_id, []}},
                                   {from, self()},  {options, ClientOpts}]),   
   
    receive
        {Client4, SID1} ->
            ct:fail(session_reused_when_session_reuse_disabled_by_server);
        {Client4, _} ->
            ok
    end,
    
    ssl_test_lib:close(Server1),
    ssl_test_lib:close(Client3),
    ssl_test_lib:close(Client4).

user_lookup(psk, _Identity, UserState) ->
    {ok, UserState};
user_lookup(srp, Username, _UserState) ->
    Salt = ssl_cipher:random_bytes(16),
    UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]),
    {ok, {srp_1024, Salt, UserPassHash}}.

test_cipher(TestCase, Config) ->
    [{name, Group} |_] = proplists:get_value(tc_group_properties, Config),
    list_to_atom(re:replace(atom_to_list(TestCase), atom_to_list(Group) ++ "_",  "", [{return, list}])).

digest() ->
    case application:get_env(ssl, protocol_version, application:get_env(ssl, dtls_protocol_version)) of
        Ver when Ver == 'tlsv1.2';
                 Ver == 'dtlsv1.2' ->
            {digest, sha256};
        _ ->
            {digest, sha1}
    end.

kill_openssl() ->
    case os:type() of
        {unix, _} ->
            os:cmd("pkill openssl");
        {win32, _} ->
            os:cmd("cmd.exe /C \"taskkill /IM openssl.exe /F\"")
    end.

hostname_format(Hostname) ->
    case lists:member($., Hostname) of
        true ->  
            Hostname;
        false ->
            "localhost"   
    end.

erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) ->
    case ssl:negotiated_protocol(Socket) of
        {ok, Protocol} ->
            active_recv(Socket, length(Data));
        Result ->
            {error, {{expected, Protocol}, {got, Result}}}
    end. 

check_openssl_npn_support(Config) ->
    HelpText = os:cmd("openssl s_client --help"),
    case string:str(HelpText, "nextprotoneg") of
        0 ->
            {skip, "Openssl not compiled with nextprotoneg support"};
        _ ->
            Config
    end.

new_config(PrivDir, ServerOpts0) ->
    CaCertFile = proplists:get_value(cacertfile, ServerOpts0),
    CertFile = proplists:get_value(certfile, ServerOpts0),
    KeyFile = proplists:get_value(keyfile, ServerOpts0),
    NewCaCertFile = filename:join(PrivDir, "new_ca.pem"),
    NewCertFile = filename:join(PrivDir, "new_cert.pem"),
    NewKeyFile = filename:join(PrivDir, "new_key.pem"),
    file:copy(CaCertFile, NewCaCertFile),
    file:copy(CertFile, NewCertFile),
    file:copy(KeyFile, NewKeyFile),
    ServerOpts1 = proplists:delete(cacertfile, ServerOpts0),
    ServerOpts2 = proplists:delete(certfile, ServerOpts1),
    ServerOpts = proplists:delete(keyfile, ServerOpts2),

    {ok, PEM} = file:read_file(NewCaCertFile),
    ct:log("CA file content: ~p~n", [public_key:pem_decode(PEM)]),

    [{cacertfile, NewCaCertFile}, {certfile, NewCertFile},
     {keyfile, NewKeyFile} | ServerOpts].

sane_openssl_alpn_npn_renegotiate() ->
     case os:cmd("openssl version") of 
         "LibreSSL 2.9.1" ++ _ ->
             false;
         "LibreSSL 2.6.4" ++ _ ->
             false;
         "OpenSSL 1.1.1a-freebsd" ++ _ ->
             false;
         _  ->
             true
     end.

openssl_sane_dtls_alpn() ->
    case os:cmd("openssl version") of
        "OpenSSL 1.1.0g" ++ _ ->
            false;
        "OpenSSL 1.1.1a" ++ _ ->
            false;
        _->
            openssl_sane_dtls()
    end.