diff options
Diffstat (limited to 'lib')
152 files changed, 13537 insertions, 11447 deletions
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl index 2305502800..2b9c1b0cf5 100644 --- a/lib/compiler/src/beam_except.erl +++ b/lib/compiler/src/beam_except.erl @@ -152,7 +152,7 @@ dig_out_fc(Arity, Is0) -> ({test,_,_,_}) -> false; (_) -> true end, Is0), - {Regs,Acc} = dig_out_fc_1(reverse(Is), Regs0, Acc0), + {Regs,Acc} = dig_out_fc_1(reverse(Is), Arity, Regs0, Acc0), case Regs of #{{x,0}:={atom,function_clause},{x,1}:=Args} -> case moves_from_stack(Args, 0, []) of @@ -165,19 +165,27 @@ dig_out_fc(Arity, Is0) -> no end. -dig_out_fc_1([{block,Bl}|Is], Regs0, Acc) -> +dig_out_fc_1([{block,Bl}|Is], Arity, Regs0, Acc) -> Regs = dig_out_fc_block(Bl, Regs0), - dig_out_fc_1(Is, Regs, Acc); -dig_out_fc_1([{bs_set_position,_,_}=I|Is], Regs, Acc) -> - dig_out_fc_1(Is, Regs, [I|Acc]); -dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Regs0, Acc) -> - Regs = prune_xregs(Live0, Regs0), - Live = dig_out_stack_live(Regs, Live0), - I = {bs_get_tail,Src,Dst,Live}, - dig_out_fc_1(Is, Regs, [I|Acc]); -dig_out_fc_1([_|_], _Regs, _Acc) -> + dig_out_fc_1(Is, Arity, Regs, Acc); +dig_out_fc_1([{bs_set_position,_,_}=I|Is], Arity, Regs, Acc) -> + dig_out_fc_1(Is, Arity, Regs, [I|Acc]); +dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Arity, Regs0, Acc) -> + case Src of + {x,X} when X < Arity -> + %% The heuristic for determining the number of live + %% registers is likely to give an incorrect result. + %% Give up. + {#{},[]}; + _ -> + Regs = prune_xregs(Live0, Regs0), + Live = dig_out_stack_live(Regs, Live0), + I = {bs_get_tail,Src,Dst,Live}, + dig_out_fc_1(Is, Arity, Regs, [I|Acc]) + end; +dig_out_fc_1([_|_], _Arity, _Regs, _Acc) -> {#{},[]}; -dig_out_fc_1([], Regs, Acc) -> +dig_out_fc_1([], _Arity, Regs, Acc) -> {Regs,Acc}. dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) -> diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index 07f4c8b461..08641e2abc 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -764,9 +764,8 @@ defined(Linear, #cg{regs=Regs}) -> def([{L,#cg_blk{is=Is0,last=Last}=Blk0}|Bs], DefMap0, Regs) -> Def0 = def_get(L, DefMap0), - {Is,Def} = def_is(Is0, Regs, Def0, []), - Successors = successors(Last), - DefMap = def_successors(Successors, Def, DefMap0), + {Is,Def,MaybeDef} = def_is(Is0, Regs, Def0, []), + DefMap = def_successors(Last, Def, MaybeDef, DefMap0), Blk = Blk0#cg_blk{is=Is}, [{L,Blk}|def(Bs, DefMap, Regs)]; def([], _, _) -> []. @@ -780,6 +779,11 @@ def_get(L, DefMap) -> def_is([#cg_alloc{anno=Anno0}=I0|Is], Regs, Def, Acc) -> I = I0#cg_alloc{anno=Anno0#{def_yregs=>Def}}, def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{op=succeeded,args=[Var]}=I], Regs, Def, Acc) -> + %% Var will only be defined on the success branch of the `br` + %% for this block. + MaybeDef = def_add_yreg(Var, [], Regs), + {reverse(Acc, [I]),Def,MaybeDef}; def_is([#cg_set{op=kill_try_tag,args=[#b_var{}=Tag]}=I|Is], Regs, Def0, Acc) -> Def = ordsets:del_element(Tag, Def0), def_is(Is, Regs, Def, [I|Acc]); @@ -822,7 +826,7 @@ def_is([#cg_set{anno=Anno0,dst=Dst}=I0|Is], Regs, Def0, Acc) -> Def = def_add_yreg(Dst, Def0, Regs), def_is(Is, Regs, Def, [I|Acc]); def_is([], _, Def, Acc) -> - {reverse(Acc),Def}. + {reverse(Acc),Def,[]}. def_add_yreg(Dst, Def, Regs) -> case is_yreg(Dst, Regs) of @@ -830,6 +834,12 @@ def_add_yreg(Dst, Def, Regs) -> false -> Def end. +def_successors(#cg_br{bool=#b_var{},succ=Succ,fail=Fail}, Def, MaybeDef, DefMap0) -> + DefMap = def_successors([Fail], ordsets:subtract(Def, MaybeDef), DefMap0), + def_successors([Succ], Def, DefMap); +def_successors(Last, Def, [], DefMap) -> + def_successors(successors(Last), Def, DefMap). + def_successors([S|Ss], Def0, DefMap) -> case DefMap of #{S:=Def1} -> diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index 64b9b3e222..e78e4647a8 100644 --- a/lib/compiler/src/beam_ssa_dead.erl +++ b/lib/compiler/src/beam_ssa_dead.erl @@ -30,7 +30,7 @@ -import(lists, [append/1,keymember/3,last/1,member/2, takewhile/2,reverse/1]). --type used_vars() :: #{beam_ssa:label():=ordsets:ordset(beam_ssa:var_name())}. +-type used_vars() :: #{beam_ssa:label():=cerl_sets:set(beam_ssa:var_name())}. -type basic_type_test() :: atom() | {'is_tagged_tuple',pos_integer(),atom()}. -type type_test() :: basic_type_test() | {'not',basic_type_test()}. @@ -90,13 +90,11 @@ shortcut_opt(#st{bs=Blocks}=St) -> %% the diff.) %% %% Unfortunately, processing the blocks in reverse post order - %% potentially makes the time complexity quadratic or even cubic if - %% the ordset of unset variables grows large, instead of - %% linear for post order processing. We try to still get reasonable - %% compilation times by optimizations that will keep the constant - %% factor as low as possible, and we try to avoid the cubic time - %% complexity by trying to keep the set of unset variables as small - %% as possible. + %% potentially makes the time complexity quadratic, instead of + %% linear for post order processing. We avoid drastic slowdowns by + %% limiting how far we search forward to a common block that + %% both the success and failure label will reach (see the comment + %% in the first clause of shortcut_2/5). Ls = beam_ssa:rpo(Blocks), shortcut_opt(Ls, #{}, St). @@ -124,10 +122,15 @@ shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br, Is, From, Bs, St0) -> St = St0#st{target=one_way}, RelOp = get_rel_op(Bool, Is), - SuccBs = bind_var(Bool, #b_literal{val=true}, Bs), + + %% The boolean in a `br` is seldom used by the successors. By + %% not binding its value unless it is actually used we might be able + %% to skip some work in shortcut/4 and sub/2. + SuccBs = bind_var_if_used(Succ0, Bool, #b_literal{val=true}, Bs, St), BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}), - FailBs = bind_var(Bool, #b_literal{val=false}, Bs), + FailBs = bind_var_if_used(Fail0, Bool, #b_literal{val=false}, Bs, St), BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}), + case {BrSucc,BrFail} of {#b_br{bool=#b_literal{val=true},succ=Succ}, #b_br{bool=#b_literal{val=true},succ=Fail}} @@ -152,8 +155,14 @@ shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) -> [{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)]; shortcut_switch([], _, _, _, _) -> []. +shortcut(L, _From, Bs, #st{rel_op=none,target=one_way}) when map_size(Bs) =:= 0 -> + %% There is no way that we can find a suitable branch, because there is no + %% relational operator stored, there are no bindings, and the block L can't + %% have any phi nodes from which we could pick bindings because when the target + %% is `one_way`, it implies the From block has a two-way `br` terminator. + #b_br{bool=#b_literal{val=true},succ=L,fail=L}; shortcut(L, From, Bs, St) -> - shortcut_1(L, From, Bs, ordsets:new(), St). + shortcut_1(L, From, Bs, cerl_sets:new(), St). shortcut_1(L, From, Bs0, UnsetVars0, St) -> case shortcut_2(L, From, Bs0, UnsetVars0, St) of @@ -170,7 +179,19 @@ shortcut_1(L, From, Bs0, UnsetVars0, St) -> end. %% Try to shortcut this block, branching to a successor. -shortcut_2(L, From, Bs0, UnsetVars0, St) -> +shortcut_2(L, From, Bs, UnsetVars, St) -> + case cerl_sets:size(UnsetVars) of + SetSize when SetSize > 128 -> + %% This is an heuristic to limit the search for a forced label + %% before it drastically slows down the compiler. Experiments + %% with scripts/diffable showed that limits larger than 31 did not + %% find any more opportunities for optimization. + none; + _SetSize -> + shortcut_3(L, From, Bs, UnsetVars, St) + end. + +shortcut_3(L, From, Bs0, UnsetVars0, St) -> #b_blk{is=Is,last=Last} = get_block(L, St), case eval_is(Is, From, Bs0, St) of none -> @@ -347,7 +368,7 @@ update_unset_vars(L, Is, Br, UnsetVars, #st{skippable=Skippable}) -> %% Some variables defined in this block are used by %% successors. We must update the set of unset variables. SetInThisBlock = [V || #b_set{dst=V} <- Is], - ordsets:union(UnsetVars, ordsets:from_list(SetInThisBlock)) + cerl_sets:union(UnsetVars, cerl_sets:from_list(SetInThisBlock)) end. shortcut_two_way(#b_br{succ=Succ,fail=Fail}, From, Bs0, UnsetVars0, St0) -> @@ -376,14 +397,14 @@ is_br_safe(UnsetVars, Br, #st{us=Us}=St) -> %% A two-way branch never branches to a phi node, so there %% is no need to check for phi nodes here. - not member(V, UnsetVars) andalso - ordsets:is_disjoint(Used0, UnsetVars) andalso - ordsets:is_disjoint(Used1, UnsetVars); + not cerl_sets:is_element(V, UnsetVars) andalso + cerl_sets:is_disjoint(Used0, UnsetVars) andalso + cerl_sets:is_disjoint(Used1, UnsetVars); #b_br{succ=Same,fail=Same} -> %% An unconditional branch must not jump to %% a phi node. not is_forbidden(Same, St) andalso - ordsets:is_disjoint(map_get(Same, Us), UnsetVars) + cerl_sets:is_disjoint(map_get(Same, Us), UnsetVars) end. is_forbidden(L, St) -> @@ -500,6 +521,15 @@ eval_switch_1([], _Arg, _PrevOp, Fail) -> %% Fail is now either the failure label or 'none'. Fail. +bind_var_if_used(L, Var, Val0, Bs, #st{us=Us}) -> + case cerl_sets:is_element(Var, map_get(L, Us)) of + true -> + Val = get_value(Val0, Bs), + Bs#{Var=>Val}; + false -> + Bs + end. + bind_var(Var, Val0, Bs) -> Val = get_value(Val0, Bs), Bs#{Var=>Val}. @@ -989,7 +1019,7 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) -> %% shortcut_opt/1. Successors = beam_ssa:successors(Blk), - Used0 = used_vars_succ(Successors, L, UsedVars0, []), + Used0 = used_vars_succ(Successors, L, UsedVars0, cerl_sets:new()), Used = used_vars_blk(Blk, Used0), UsedVars = used_vars_phis(Is, L, Used, UsedVars0), @@ -1000,8 +1030,8 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) -> %% shortcut_opt/1. Defined0 = [Def || #b_set{dst=Def} <- Is], - Defined = ordsets:from_list(Defined0), - MaySkip = ordsets:is_disjoint(Defined, Used0), + Defined = cerl_sets:from_list(Defined0), + MaySkip = cerl_sets:is_disjoint(Defined, Used0), case MaySkip of true -> Skip = Skip0#{L=>true}, @@ -1018,11 +1048,11 @@ used_vars_succ([S|Ss], L, LiveMap, Live0) -> #{Key:=Live} -> %% The successor has a phi node, and the value for %% this block in the phi node is a variable. - used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0)); + used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0)); #{S:=Live} -> %% No phi node in the successor, or the value for %% this block in the phi node is a literal. - used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0)); + used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0)); #{} -> %% A peek_message block which has not been processed yet. used_vars_succ(Ss, L, LiveMap, Live0) @@ -1040,7 +1070,7 @@ used_vars_phis(Is, L, Live0, UsedVars0) -> case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of [_|_]=PhiVars -> PhiLive0 = rel2fam(PhiVars), - PhiLive = [{{L,P},ordsets:union(ordsets:from_list(Vs), Live0)} || + PhiLive = [{{L,P},cerl_sets:union(cerl_sets:from_list(Vs), Live0)} || {P,Vs} <- PhiLive0], maps:merge(UsedVars, maps:from_list(PhiLive)); [] -> @@ -1050,14 +1080,14 @@ used_vars_phis(Is, L, Live0, UsedVars0) -> end. used_vars_blk(#b_blk{is=Is,last=Last}, Used0) -> - Used = ordsets:union(Used0, beam_ssa:used(Last)), + Used = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(Last))), used_vars_is(reverse(Is), Used). used_vars_is([#b_set{op=phi}|Is], Used) -> used_vars_is(Is, Used); used_vars_is([#b_set{dst=Dst}=I|Is], Used0) -> - Used1 = ordsets:union(Used0, beam_ssa:used(I)), - Used = ordsets:del_element(Dst, Used1), + Used1 = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(I))), + Used = cerl_sets:del_element(Dst, Used1), used_vars_is(Is, Used); used_vars_is([], Used) -> Used. @@ -1066,8 +1096,9 @@ used_vars_is([], Used) -> %%% Common utilities. %%% -sub(#b_set{args=Args}=I, Sub) -> - I#b_set{args=[sub_arg(A, Sub) || A <- Args]}. +sub(#b_set{args=Args}=I, Sub) when map_size(Sub) =/= 0 -> + I#b_set{args=[sub_arg(A, Sub) || A <- Args]}; +sub(I, _Sub) -> I. sub_arg(#b_var{}=Old, Sub) -> case Sub of diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 229edc6a1d..d87c66c272 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -1939,12 +1939,24 @@ verify_merge_is(_) -> is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=peek_message}|_]}) -> false; -is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{}) -> +is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{is=Is}) -> %% The predecessor block must have exactly one successor (L) for %% the merge to be safe. case beam_ssa:successors(Blk) of - [L] -> true; - [_|_] -> false + [L] -> + case Is of + [#b_set{op=phi,args=[_]}|_] -> + %% The type optimizer pass must have been + %% turned off, since it would have removed this + %% redundant phi node. Refuse to merge the blocks + %% to ensure that this phi node remains at the + %% beginning of a block. + false; + _ -> + true + end; + [_|_] -> + false end; is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) -> false. diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 7ef604d444..7f816b9802 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -1463,25 +1463,51 @@ fix_receive([], _Defs, Blocks, Count) -> {Blocks,Count}. %% find_loop_exit([Label], Blocks) -> Label | none. -%% Find the block to which control is transferred when the -%% the receive loop is exited. - -find_loop_exit([L1,L2|_Ls], Blocks) -> - Path1 = beam_ssa:rpo([L1], Blocks), - Path2 = beam_ssa:rpo([L2], Blocks), - find_loop_exit_1(Path1, cerl_sets:from_list(Path2)); -find_loop_exit(_, _) -> none. - -find_loop_exit_1([?BADARG_BLOCK | T], OtherPath) -> - %% ?BADARG_BLOCK is a marker and not an actual block, so we can't consider - %% it to be a common block even if both paths cross it. - find_loop_exit_1(T, OtherPath); -find_loop_exit_1([H|T], OtherPath) -> - case cerl_sets:is_element(H, OtherPath) of - true -> H; - false -> find_loop_exit_1(T, OtherPath) +%% Given the list of all blocks with the remove_message instructions +%% for this receive, find the block to which control is transferred +%% when the receive loop is exited (if any). + +find_loop_exit([_,_|_]=RmBlocks, Blocks) -> + %% We used to only analyze the path from two of the remove_message + %% blocks. That would fail to find a common block if one or both + %% of the blocks happened to raise an exception. To be sure that + %% we always find a common block if there is one (shared by at + %% least two clauses), we must analyze the path from all + %% remove_message blocks. + {Dominators,_} = beam_ssa:dominators(Blocks), + RmSet = cerl_sets:from_list(RmBlocks), + Rpo = beam_ssa:rpo(RmBlocks, Blocks), + find_loop_exit_1(Rpo, RmSet, Dominators); +find_loop_exit(_, _) -> + %% There is (at most) a single clause. There is no common + %% loop exit block. + none. + +find_loop_exit_1([?BADARG_BLOCK|Ls], RmSet, Dominators) -> + %% ?BADARG_BLOCK is a marker and not an actual block, so it is not + %% the block we are looking for. + find_loop_exit_1(Ls, RmSet, Dominators); +find_loop_exit_1([L|Ls], RmSet, Dominators) -> + DomBy = map_get(L, Dominators), + case any(fun(E) -> cerl_sets:is_element(E, RmSet) end, DomBy) of + true -> + %% This block is dominated by one of the remove_message blocks, + %% which means that the block is part of only one clause. + %% It is not the block we are looking for. + find_loop_exit_1(Ls, RmSet, Dominators); + false -> + %% This block is the first block that is not dominated by + %% any of the blocks with remove_message instructions, + %% which means that at least two of the receive clauses + %% will ultimately transfer control to it. It is the block + %% we are looking for. + L end; -find_loop_exit_1([], _) -> none. +find_loop_exit_1([], _, _) -> + %% None of clauses transfers control to a common block after the receive + %% statement. That means that the receive statement is a the end of a + %% function (or that all clauses raise exceptions). + none. %% find_rm_blocks(StartLabel, Blocks) -> [Label]. %% Find all blocks that start with remove_message within the receive diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl index 426efa2cc9..73983bd34a 100644 --- a/lib/compiler/src/beam_ssa_share.erl +++ b/lib/compiler/src/beam_ssa_share.erl @@ -303,8 +303,12 @@ canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) -> Acc0 end, {{ret,canonical_arg(Arg, VarMap),Acc1},VarMap}; -canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) -> - {{br,succ,Fail,Acc},VarMap}; +canonical_is([#b_br{bool=#b_var{}=Arg,fail=Fail}], VarMap, Acc) -> + %% A previous buggy version of this code omitted the canonicalized + %% argument in the return value. Unfortunately, that worked most + %% of the time, except when `br` terminator referenced a variable + %% defined in a previous block instead of in the same block. + {{br,canonical_arg(Arg, VarMap),succ,Fail,Acc},VarMap}; canonical_is([#b_br{succ=Succ}], VarMap, Acc) -> {{br,Succ,Acc},VarMap}; canonical_is([], VarMap, Acc) -> diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 68920e7dd3..3c06c83e2e 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -160,6 +160,10 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) -> case join(maps:values(TypeMap)) of any -> opt_finish_1(Args, TypeMaps, ParamInfo0); + none -> + %% This function will never be called. Pretend that we don't + %% know the type for this argument. + opt_finish_1(Args, TypeMaps, ParamInfo0); JoinedType -> JoinedType = verified_type(JoinedType), ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) }, diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index ebe9631e09..349d74eb58 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -1068,8 +1068,11 @@ verify_get_map(Fail, Src, List, Vst0) -> %% {get_map_elements,{f,7},{x,1},{list,[{atom,a},{x,1},{atom,b},{x,2}]}}. %% %% If 'a' exists but not 'b', {x,1} is overwritten when we jump to {f,7}. +%% +%% We must be careful to preserve the uninitialized status for Y registers +%% that have been allocated but not yet defined. clobber_map_vals([Key,Dst|T], Map, Vst0) -> - case is_reg_defined(Dst, Vst0) of + case is_reg_initialized(Dst, Vst0) of true -> Vst = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vst0), clobber_map_vals(T, Map, Vst); @@ -1079,6 +1082,17 @@ clobber_map_vals([Key,Dst|T], Map, Vst0) -> clobber_map_vals([], _Map, Vst) -> Vst. +is_reg_initialized({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> + is_map_key(Reg, Xs); +is_reg_initialized({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> + case Ys of + #{Reg:=Val} -> + Val =/= uninitialized; + #{} -> + false + end; +is_reg_initialized(V, #vst{}) -> error({not_a_register, V}). + extract_map_keys([Key,_Val|T]) -> [Key|extract_map_keys(T)]; extract_map_keys([]) -> []. @@ -1604,13 +1618,8 @@ infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) -> end; infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) -> fun(Val, S) -> - case is_value_alive(Tuple, S) of - true -> - Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }}, - update_type(fun meet/2, Type, Tuple, S); - false -> - S - end + Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }}, + update_type(fun meet/2, Type, Tuple, S) end; infer_types_1(#value{op={bif,is_atom},args=[Src]}) -> infer_type_test_bif({atom,[]}, Src); @@ -1634,10 +1643,7 @@ infer_types_1(#value{op={bif,is_tuple},args=[Src]}) -> infer_type_test_bif({tuple,[0],#{}}, Src); infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) -> fun({integer,Arity}, S) -> - case is_value_alive(Tuple, S) of - true -> update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); - false -> S - end; + update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); (_, S) -> S end; infer_types_1(_) -> @@ -1645,10 +1651,7 @@ infer_types_1(_) -> infer_type_test_bif(Type, Src) -> fun({atom,true}, S) -> - case is_value_alive(Src, S) of - true -> update_type(fun meet/2, Type, Src, S); - false -> S - end; + update_type(fun meet/2, Type, Src, S); (_, S) -> S end. @@ -1885,10 +1888,6 @@ check_try_catch_tags(Type, {y,N}=Reg, Vst) -> ok end. -is_reg_defined({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> is_map_key(Reg, Xs); -is_reg_defined({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> is_map_key(Reg, Ys); -is_reg_defined(V, #vst{}) -> error({not_a_register, V}). - assert_term(Src, Vst) -> _ = get_term_type(Src, Vst), ok. @@ -2285,9 +2284,6 @@ get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> get_raw_type(Src, #vst{}) -> get_literal_type(Src). -is_value_alive(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> - is_map_key(Ref, Vs). - get_literal_type(nil=T) -> T; get_literal_type({atom,A}=T) when is_atom(A) -> T; get_literal_type({float,F}=T) when is_float(F) -> T; @@ -2469,25 +2465,44 @@ merge_vrefs(RefA, RefB, Merge, Counter) -> merge_values(Merge, VsA, VsB) -> maps:fold(fun(Spec, New, Acc) -> - merge_values_1(Spec, New, VsA, VsB, Acc) + mv_1(Spec, New, VsA, VsB, Acc) end, #{}, Merge). -merge_values_1(Same, Same, VsA, VsB, Acc) -> +mv_1(Same, Same, VsA, VsB, Acc0) -> %% We're merging different versions of the same value, so it's safe to %% reuse old entries if the type's unchanged. - #value{type=TypeA}=EntryA = map_get(Same, VsA), - #value{type=TypeB}=EntryB = map_get(Same, VsB), + #value{type=TypeA,args=Args}=EntryA = map_get(Same, VsA), + #value{type=TypeB,args=Args}=EntryB = map_get(Same, VsB), + Entry = case join(TypeA, TypeB) of TypeA -> EntryA; TypeB -> EntryB; JoinedType -> EntryA#value{type=JoinedType} end, - Acc#{ Same => Entry }; -merge_values_1({RefA, RefB}, New, VsA, VsB, Acc) -> + + Acc = Acc0#{ Same => Entry }, + + %% Type inference may depend on values that are no longer reachable from a + %% register, so all arguments must be merged into the new state. + mv_args(Args, VsA, VsB, Acc); +mv_1({RefA, RefB}, New, VsA, VsB, Acc) -> #value{type=TypeA} = map_get(RefA, VsA), #value{type=TypeB} = map_get(RefB, VsB), Acc#{ New => #value{op=join,args=[],type=join(TypeA, TypeB)} }. +mv_args([#value_ref{}=Arg | Args], VsA, VsB, Acc0) -> + case Acc0 of + #{ Arg := _ } -> + mv_args(Args, VsA, VsB, Acc0); + #{} -> + Acc = mv_1(Arg, Arg, VsA, VsB, Acc0), + mv_args(Args, VsA, VsB, Acc) + end; +mv_args([_ | Args], VsA, VsB, Acc) -> + mv_args(Args, VsA, VsB, Acc); +mv_args([], _VsA, _VsB, Acc) -> + Acc. + merge_fragility(FragileA, FragileB) -> cerl_sets:union(FragileA, FragileB). diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 28db8986ff..0325c714d0 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -268,8 +268,11 @@ expand_opt(r21, Os) -> [no_put_tuple2 | expand_opt(no_bsm3, Os)]; expand_opt({debug_info_key,_}=O, Os) -> [encrypt_debug_info,O|Os]; -expand_opt(no_type_opt, Os) -> - [no_ssa_opt_type_start, +expand_opt(no_type_opt=O, Os) -> + %% Be sure to keep the no_type_opt option so that it will + %% be recorded in the BEAM file, allowing the test suites + %% to recompile the file with this option. + [O,no_ssa_opt_type_start, no_ssa_opt_type_continue, no_ssa_opt_type_finish | Os]; expand_opt(O, Os) -> [O|Os]. diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index db8eb7e2e1..7be23fbb93 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -109,6 +109,8 @@ NO_MOD_OPT = $(NO_OPT) NO_SSA_OPT = $(NO_OPT) +NO_TYPE_OPT = $(NO_OPT) + NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE) NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl) POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE) @@ -121,6 +123,8 @@ NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE) NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl) NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE) NO_SSA_OPT_ERL_FILES= $(NO_SSA_OPT_MODULES:%=%.erl) +NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE) +NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) CORE_FILES= $(CORE_MODULES:%=%.core) @@ -150,7 +154,7 @@ EBIN = . # ---------------------------------------------------- make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \ - $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) + $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \ @@ -169,6 +173,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \ + -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE) tests debug opt: make_emakefile erl $(ERL_MAKE_FLAGS) -make @@ -202,6 +208,10 @@ docs: %_no_module_opt_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_no_type_opt_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- @@ -216,7 +226,8 @@ release_tests_spec: make_emakefile $(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ $(INLINE_ERL_FILES) $(R21_ERL_FILES) \ $(NO_MOD_OPT_ERL_FILES) \ - $(NO_SSA_OPT_ERL_FILES) "$(RELSYSDIR)" + $(NO_SSA_OPT_ERL_FILES) \ + $(NO_TYPE_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)" for file in $(ERL_DUMMY_FILES); do \ module=`basename $$file .erl`; \ diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl index 67947dc292..f52239f2a8 100644 --- a/lib/compiler/test/beam_except_SUITE.erl +++ b/lib/compiler/test/beam_except_SUITE.erl @@ -72,11 +72,25 @@ bs_get_tail(Config) -> {function_clause, [{?MODULE,bs_get_tail_1,[<<>>,0,0,Config],_}|_]}} = (catch bs_get_tail_1(id(<<>>), 0, 0, Config)), + + ok = bs_get_tail_2(<<"W">>, <<"X">>, <<"Z">>), + ok = bs_get_tail_2(<<"M">>, <<"X">>, <<"Z">>), + {'EXIT', + {function_clause, + [{?MODULE,do_get_bs_tail_2,[<<"A">>,<<"B">>,[],<<"C">>],_}|_]}} = + (catch bs_get_tail_2(<<"A">>, <<"B">>, <<"C">>)), + ok. bs_get_tail_1(<<_:32, Rest/binary>>, Z1, Z2, F1) -> {Rest,Z1,Z2,F1}. +bs_get_tail_2(A, B, C) -> + do_get_bs_tail_2(A, B, [], C). + +do_get_bs_tail_2(<<"W">>, <<"X">>, _, <<"Z">>) -> ok; +do_get_bs_tail_2(<<"M">>, <<"X">>, _, <<"Z">>) -> ok. + coverage(_) -> File = {file,"fake.erl"}, ok = fc(a), diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index dd1b7ddcd3..3b510f3528 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -23,7 +23,7 @@ init_per_group/2,end_per_group/2, calls/1,tuple_matching/1,recv/1,maps/1, cover_ssa_dead/1,combine_sw/1,share_opt/1, - beam_ssa_dead_crash/1]). + beam_ssa_dead_crash/1,stack_init/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -39,7 +39,8 @@ groups() -> cover_ssa_dead, combine_sw, share_opt, - beam_ssa_dead_crash + beam_ssa_dead_crash, + stack_init ]}]. init_per_suite(Config) -> @@ -524,9 +525,11 @@ do_comb_sw_2(X) -> erase(?MODULE). share_opt(_Config) -> - ok = do_share_opt(0). + ok = do_share_opt_1(0), + ok = do_share_opt_2(), + ok. -do_share_opt(A) -> +do_share_opt_1(A) -> %% The compiler would be stuck in an infinite loop in beam_ssa_share. case A of 0 -> a; @@ -535,6 +538,26 @@ do_share_opt(A) -> end, receive after 1 -> ok end. +do_share_opt_2() -> + ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok), + ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok), + ok = sopt_2({[], []}, ok), + ok. + +sopt_2({Flags, Opts}, ok) -> + Broadcast = lists:member(broadcast, Flags), + P2P = lists:member(pointtopoint, Flags), + case Opts of + %% The following two clauses would be combined to one, silently + %% discarding the guard test of the P2P variable. + [{broadaddr,_}|Os] when Broadcast -> + sopt_2({Flags, Os}, ok); + [{dstaddr,_}|Os] when P2P -> + sopt_2({Flags, Os}, ok); + [] -> + ok + end. + beam_ssa_dead_crash(_Config) -> not_A_B = do_beam_ssa_dead_crash(id(false), id(true)), not_A_not_B = do_beam_ssa_dead_crash(false, false), @@ -589,6 +612,30 @@ do_beam_ssa_dead_crash(A, B) -> end end. +stack_init(_Config) -> + 6 = stack_init(a, #{a => [1,2,3]}), + 0 = stack_init(missing, #{}), + ok. + +stack_init(Key, Map) -> + %% beam_ssa_codegen would wrongly assume that y(0) would always be + %% initialized by the `get_map_elements` instruction that follows, and + %% would set up the stack frame using an `allocate` instruction and + %% would not generate an `init` instruction to initialize y(0). + Res = case Map of + #{Key := Elements} -> + %% Elements will be assigned to y(0) if the key Key exists. + lists:foldl(fun(El, Acc) -> + Acc + El + end, 0, Elements); + #{} -> + %% y(0) will be left uninitialized when the key is not + %% present in the map. + 0 + end, + %% y(0) would be uninitialized here if the key was not present in the map + %% (if the second clause was executed). + id(Res). %% The identity function. id(I) -> I. diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl index 076a604aa4..a99dee48aa 100644 --- a/lib/compiler/test/beam_type_SUITE.erl +++ b/lib/compiler/test/beam_type_SUITE.erl @@ -24,7 +24,8 @@ integers/1,numbers/1,coverage/1,booleans/1,setelement/1, cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1, arity_checks/1,elixir_binaries/1,find_best/1, - test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1]). + test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1, + none_argument/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -49,7 +50,8 @@ groups() -> test_size, cover_lists_functions, list_append, - bad_binary_unit + bad_binary_unit, + none_argument ]}]. init_per_suite(Config) -> @@ -518,5 +520,24 @@ bad_binary_unit(_Config) -> false = is_binary(Bitstring), ok. +%% ERL-1013: The compiler would crash during the type optimization pass. +none_argument(_Config) -> + Binary = id(<<3:16, 42>>), + error = id(case Binary of + <<Len:16, Body/binary>> when length(Body) == Len - 2 -> + %% The type for Body will be none. It means + %% that this clause will never match and that + %% uncompress/1 will never be called. + uncompress(Body); + _ -> + error + end), + ok. + +uncompress(CompressedBinary) -> + %% The type for CompressedBinary is none, which beam_ssa_type + %% did not handle properly. + zlib:uncompress(CompressedBinary). + id(I) -> I. diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 6b1438abdd..20f6cb2691 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -681,11 +681,16 @@ infer_on_eq_4(T) -> %% ERIERL-348; types were inferred for dead values, causing validation to fail. +-record(idv, {key}). + infer_dead_value(Config) when is_list(Config) -> a = idv_1({a, b, c, d, e, f, g}, {0, 0, 0, 0, 0, 0, 0}), b = idv_1({a, b, c, d, 0, 0, 0}, {a, b, c, d, 0, 0, 0}), c = idv_1({0, 0, 0, 0, 0, f, g}, {0, 0, 0, 0, 0, f, g}), error = idv_1(gurka, gaffel), + + ok = idv_2(id(#idv{})), + ok. idv_1({_A, _B, _C, _D, _E, _F, _G}, @@ -700,6 +705,23 @@ idv_1({_A, _B, _C, _D, _E, F, G}, idv_1(_A, _B) -> error. +%% ERL-995: The first solution to ERIERL-348 was incomplete and caused +%% validation to fail when living values depended on delayed type inference on +%% "dead" values. + +idv_2(State) -> + Flag = (State#idv.key == undefined), + case id(gurka) of + {_} -> id([Flag]); + _ -> ok + end, + if + Flag -> idv_called_once(State); + true -> ok + end. + +idv_called_once(_State) -> ok. + %%%------------------------------------------------------------------------- transform_remove(Remove, Module) -> diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl index 752491f0f8..8cd864c59e 100644 --- a/lib/compiler/test/receive_SUITE.erl +++ b/lib/compiler/test/receive_SUITE.erl @@ -431,6 +431,20 @@ elusive_common_exit(_Config) -> self() ! {1, a}, self() ! {2, b}, {[z], [{2,b},{1,a}]} = elusive_loop([x,y,z], 2, []), + + CodeServer = whereis(code_server), + Self = self(), + Self ! {Self, abc}, + Self ! {CodeServer, []}, + Self ! {Self, other}, + try elusive2([]) of + Unexpected -> + ct:fail("Expected an exception; got ~p\n", [Unexpected]) + catch + throw:[other, CodeServer, Self] -> + ok + end, + ok. elusive_loop(List, 0, Results) -> @@ -449,4 +463,25 @@ elusive_loop(List, ToReceive, Results) -> %% that it would not insert all necessary copy instructions. elusive_loop(RemList, ToReceive-1, [Result | Results]). + +elusive2(Acc) -> + receive + {Pid, abc} -> + ok; + {Pid, []} -> + ok; + {Pid, Res} -> + %% beam_ssa_pre_codegen:find_loop_exit/2 attempts to find + %% the first block of the common code after the receive + %% statement. It used to only look at the two last clauses + %% of the receive. In this function, the last two clauses + %% don't have any common block, so it would be assumed + %% that there was no common block for any of the + %% clauses. That would mean that copy instructions would + %% not be inserted as needed. + throw([Res | Acc]) + end, + %% Common code. + elusive2([Pid | Acc]). + id(I) -> I. diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 3348c6e9ea..34410e4b2a 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -97,7 +97,8 @@ get_data_dir(Config) -> Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts), Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts), Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts), - Data = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts), + Data5 = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts), + Data = re:replace(Data5, "_no_type_opt_SUITE", "_SUITE", Opts), re:replace(Data, "_no_ssa_opt_SUITE", "_SUITE", Opts). is_cloned_mod(Mod) -> diff --git a/lib/erl_interface/src/decode/decode_fun.c b/lib/erl_interface/src/decode/decode_fun.c index db71007505..76dc0e2ab8 100644 --- a/lib/erl_interface/src/decode/decode_fun.c +++ b/lib/erl_interface/src/decode/decode_fun.c @@ -52,7 +52,10 @@ int ei_decode_fun(const char *buf, int *index, erlang_fun *p) switch (get8(s)) { case ERL_FUN_EXT: /* mark as old (R7 and older) external fun */ - if (p != NULL) p->arity = -1; + if (p != NULL) { + p->type = EI_FUN_CLOSURE; + p->arity = -1; + } /* first number of free vars (environment) */ n = get32be(s); /* then the pid */ diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java index 187705a0b5..5e777c1164 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -106,9 +106,9 @@ public class OtpOutputStream extends ByteArrayOutputStream { } /** - * Trims the capacity of this <tt>OtpOutputStream</tt> instance to be the + * Trims the capacity of this <code>OtpOutputStream</code> instance to be the * buffer's current size. An application can use this operation to minimize - * the storage of an <tt>OtpOutputStream</tt> instance. + * the storage of an <code>OtpOutputStream</code> instance. */ public void trimToSize() { resize(super.count); @@ -125,7 +125,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { } /** - * Increases the capacity of this <tt>OtpOutputStream</tt> instance, if + * Increases the capacity of this <code>OtpOutputStream</code> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * @@ -939,7 +939,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { * @param o * the Erlang term to write. * @param level - * the compression level (<tt>0..9</tt>) + * the compression level (<code>0..9</code>) */ public void write_compressed(final OtpErlangObject o, final int level) { @SuppressWarnings("resource") diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml index aa577f3c62..3d8e82ce7c 100644 --- a/lib/kernel/doc/src/logger_disk_log_h.xml +++ b/lib/kernel/doc/src/logger_disk_log_h.xml @@ -133,7 +133,7 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h, #{config => #{file => "./my_disk_log", type => wrap, max_no_files => 4, - max_no_bytes => 10000}, + max_no_bytes => 10000, filesync_repeat_interval => 1000}}). </code> <p>To use the disk_log handler instead of the default standard diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl index 8eab36e308..5bff9cc292 100644 --- a/lib/kernel/test/global_SUITE.erl +++ b/lib/kernel/test/global_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2018. All Rights Reserved. +%% Copyright Ericsson AB 1997-2019. 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. @@ -2512,8 +2512,10 @@ re_register_name(Config) when is_list(Config) -> Me = self(), Pid1 = spawn(fun() -> proc(Me) end), yes = global:register_name(name, Pid1), + wait_for_monitor(Pid1), Pid2 = spawn(fun() -> proc(Me) end), _ = global:re_register_name(name, Pid2), + wait_for_monitor(Pid2), Pid2 ! die, Pid1 ! die, receive {Pid1, MonitoredBy1} -> [] = MonitoredBy1 end, @@ -2522,6 +2524,15 @@ re_register_name(Config) when is_list(Config) -> init_condition(Config), ok. +wait_for_monitor(Pid) -> + case process_info(Pid, monitored_by) of + {monitored_by, []} -> + timer:sleep(1), + wait_for_monitor(Pid); + {monitored_by, [_]} -> + ok + end. + proc(Parent) -> receive die -> ok end, {monitored_by, MonitoredBy} = process_info(self(), monitored_by), diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl index 298a364a91..173e25c520 100644 --- a/lib/kernel/test/interactive_shell_SUITE.erl +++ b/lib/kernel/test/interactive_shell_SUITE.erl @@ -23,7 +23,8 @@ init_per_group/2,end_per_group/2, get_columns_and_rows/1, exit_initial/1, job_control_local/1, job_control_remote/1, - job_control_remote_noshell/1,ctrl_keys/1]). + job_control_remote_noshell/1,ctrl_keys/1, + get_columns_and_rows_escript/1]). -export([init_per_testcase/2, end_per_testcase/2]). %% For spawn @@ -40,7 +41,8 @@ suite() -> {timetrap,{minutes,3}}]. all() -> - [get_columns_and_rows, exit_initial, job_control_local, + [get_columns_and_rows_escript,get_columns_and_rows, + exit_initial, job_control_local, job_control_remote, job_control_remote_noshell, ctrl_keys]. @@ -72,6 +74,60 @@ end_per_group(_GroupName, Config) -> -define(dbg(Data),noop). -endif. +string_to_term(Str) -> + {ok,Tokens,_EndLine} = erl_scan:string(Str ++ "."), + {ok,AbsForm} = erl_parse:parse_exprs(Tokens), + {value,Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()), + Value. + +run_unbuffer_escript(Rows, Columns, EScript, NoTermStdIn, NoTermStdOut) -> + DataDir = filename:join(filename:dirname(code:which(?MODULE)), "interactive_shell_SUITE_data"), + TmpFile = filename:join(DataDir, "tmp"), + ok = file:write_file(TmpFile, <<>>), + CommandModifier = + case {NoTermStdIn, NoTermStdOut} of + {false, false} -> ""; + {true, false} -> io_lib:format(" < ~s", [TmpFile]); + {false, true} -> io_lib:format(" > ~s ; cat ~s", [TmpFile, TmpFile]); + {true, true} -> io_lib:format(" > ~s < ~s ; cat ~s", [TmpFile, TmpFile, TmpFile]) + end, + Command = io_lib:format("unbuffer -p bash -c \"stty rows ~p; stty columns ~p; escript ~s ~s\"", + [Rows, Columns, EScript, CommandModifier]), + %% io:format("Command: ~s ~n", [Command]), + Out = os:cmd(Command), + %% io:format("Out: ~p ~n", [Out]), + string_to_term(Out). + +get_columns_and_rows_escript(Config) when is_list(Config) -> + ExpectUnbufferInstalled = + try + "79" = string:trim(os:cmd("unbuffer -p bash -c \"stty columns 79 ; tput cols\"")), + true + catch + _:_ -> false + end, + case ExpectUnbufferInstalled of + false -> + {skip, + "The unbuffer tool (https://core.tcl-lang.org/expect/index) does not seem to be installed.~n" + "On Ubuntu/Debian: \"sudo apt-get install expect\""}; + true -> + DataDir = filename:join(filename:dirname(code:which(?MODULE)), "interactive_shell_SUITE_data"), + IoColumnsErl = filename:join(DataDir, "io_columns.erl"), + IoRowsErl = filename:join(DataDir, "io_rows.erl"), + [ + begin + {ok, 42} = run_unbuffer_escript(99, 42, IoColumnsErl, NoTermStdIn, NoTermStdOut), + {ok, 99} = run_unbuffer_escript(99, 42, IoRowsErl, NoTermStdIn, NoTermStdOut) + end + || + {NoTermStdIn, NoTermStdOut} <- [{false, false}, {true, false}, {false, true}] + ], + {error,enotsup} = run_unbuffer_escript(99, 42, IoRowsErl, true, true), + {error,enotsup} = run_unbuffer_escript(99, 42, IoColumnsErl, true, true), + ok + end. + %% Test that the shell can access columns and rows. get_columns_and_rows(Config) when is_list(Config) -> case proplists:get_value(default_shell,Config) of diff --git a/lib/kernel/test/interactive_shell_SUITE_data/.gitignore b/lib/kernel/test/interactive_shell_SUITE_data/.gitignore new file mode 100644 index 0000000000..1c2f433de1 --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/.gitignore @@ -0,0 +1 @@ +tmp
\ No newline at end of file diff --git a/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl b/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl new file mode 100644 index 0000000000..32d0cf25df --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl @@ -0,0 +1,6 @@ +-module(io_columns). + +-export([main/1]). + +main(_) -> + io:format("~p",[io:columns()]). diff --git a/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl b/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl new file mode 100644 index 0000000000..53ceb464b0 --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl @@ -0,0 +1,6 @@ +-module(io_rows). + +-export([main/1]). + +main(_) -> + io:format("~p",[io:rows()]). diff --git a/lib/megaco/test/Makefile b/lib/megaco/test/Makefile index 4ddd73eea1..b4e31765b8 100644 --- a/lib/megaco/test/Makefile +++ b/lib/megaco/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2016. All Rights Reserved. +# Copyright Ericsson AB 1999-2019. 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. @@ -102,6 +102,9 @@ endif ERL_COMPILE_FLAGS += $(MEGACO_ERL_COMPILE_FLAGS) +# We have a behaviour in the test catalog (megaco_test_generator) +ERL_COMPILE_FLAGS += -pa ../../megaco/test + ERL_PATH = -pa ../../megaco/examples/simple \ -pa ../../megaco/ebin \ -pa ../../et/ebin diff --git a/lib/megaco/test/megaco_SUITE.erl b/lib/megaco/test/megaco_SUITE.erl index 38590f9fee..f7b8ffe032 100644 --- a/lib/megaco/test/megaco_SUITE.erl +++ b/lib/megaco/test/megaco_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -25,7 +25,21 @@ -module(megaco_SUITE). --compile(export_all). +-export([ + suite/0, + all/0, + groups/0, + + init_per_suite/1, + end_per_suite/1, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + init/0 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -96,6 +110,20 @@ groups() -> {flex, [], [{megaco_flex_test, all}]}]. init_per_suite(Config) -> + io:format("~w:init_per_suite -> entry with" + "~n Config: ~p" + "~n OS Type: ~p" + "~n OS Version: ~s" + "~n", + [?MODULE, + Config, + os:type(), + case os:version() of + {Major, Minor, Release} -> + ?F("~w.~w.~w", [Major, Minor, Release]); + Str when is_list(Str) -> + Str + end]), Config. end_per_suite(_Config) -> diff --git a/lib/megaco/test/megaco_actions_test.erl b/lib/megaco/test/megaco_actions_test.erl index fcbe4f12fa..498e5c91cb 100644 --- a/lib/megaco/test/megaco_actions_test.erl +++ b/lib/megaco/test/megaco_actions_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2019. 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. @@ -20,13 +20,30 @@ %% %%---------------------------------------------------------------------- -%% Purpose: Verify that it is possible to separatelly encode +%% Purpose: Verify that it is possible to separately encode %% the action requests list. Do this with all codec's %% that supports partial encode. %%---------------------------------------------------------------------- -module(megaco_actions_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + + pretty_text/1, + flex_pretty_text/1, + compact_text/1, + flex_compact_text/1, + erl_dist/1, + erl_dist_mc/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -364,9 +381,6 @@ sleep(X) -> receive after X -> ok end. -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i(F) -> @@ -376,8 +390,8 @@ i(F, A) -> print(info, get(verbosity), "", F, A). -d(F) -> - d(F, []). +%% d(F) -> +%% d(F, []). d(F, A) -> print(debug, get(verbosity), "DBG: ", F, A). @@ -391,20 +405,10 @@ print(Severity, Verbosity, P, F, A) -> print(printable(Severity,Verbosity), P, F, A). print(true, P, F, A) -> - io:format("~s~p:~s: " ++ F ++ "~n", [P, self(), get(sname) | A]); + io:format("*** [~s] ~s ~p ~s ***" + "~n " ++ F ++ "~n", + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - diff --git a/lib/megaco/test/megaco_app_test.erl b/lib/megaco/test/megaco_app_test.erl index 981d93f5dd..fff2d8c7d4 100644 --- a/lib/megaco/test/megaco_app_test.erl +++ b/lib/megaco/test/megaco_app_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2019. 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. @@ -20,29 +20,42 @@ %%---------------------------------------------------------------------- %% Purpose: Verify the application specifics of the Megaco application %%---------------------------------------------------------------------- + -module(megaco_app_test). --compile(export_all). +-export([ + all/0, + + app/0, app/1, + appup/0, appup/1 + ]). -include_lib("common_test/include/ct.hrl"). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- + all() -> [ app, appup ]. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- + app() -> [{doc, "Test that the megaco app file is ok"}]. app(Config) when is_list(Config) -> ok = test_server:app_test(megaco). + + %%-------------------------------------------------------------------- + appup() -> [{doc, "Test that the megaco appup file is ok"}]. appup(Config) when is_list(Config) -> diff --git a/lib/megaco/test/megaco_appup_test.erl b/lib/megaco/test/megaco_appup_test.erl index 8dc3ad51a0..a06d274844 100644 --- a/lib/megaco/test/megaco_appup_test.erl +++ b/lib/megaco/test/megaco_appup_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2019. 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. @@ -22,8 +22,23 @@ %%---------------------------------------------------------------------- -module(megaco_appup_test). --compile(export_all). --compile({no_auto_import,[error/1]}). +-export([ + all/0, + groups/0, + + init_per_suite/1, + end_per_suite/1, + + init_per_group/2, + end_per_group/2, + + init_per_testcase/2, + end_per_testcase/2, + + appup_file/1 + ]). + +-compile({no_auto_import, [error/1]}). -include_lib("common_test/include/ct.hrl"). -include("megaco_test_lib.hrl"). @@ -76,6 +91,10 @@ end_per_testcase(_Case, Config) when is_list(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Perform a simple check of the appup file +appup_file(suite) -> + []; +appup_file(doc) -> + ["Perform a simple check of the appup file"]; appup_file(Config) when is_list(Config) -> ok = ?t:appup_test(megaco). diff --git a/lib/megaco/test/megaco_call_flow_test.erl b/lib/megaco/test/megaco_call_flow_test.erl index eb4574862d..03caf705ba 100644 --- a/lib/megaco/test/megaco_call_flow_test.erl +++ b/lib/megaco/test/megaco_call_flow_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -38,7 +38,85 @@ -module(megaco_call_flow_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + pretty/1, + compact/1, + pretty_flex/1, + compact_flex/1, + bin/1, + ber/1, + per/1, + standard_erl/1, + compressed_erl/1, + + t/0, t/1 + + ]). + +-export([ + msg1/0, msg1/1, + msg2/0, msg2/1, + msg3/0, msg3/1, + msg4/0, msg4/1, + msg5a/0, msg5a/1, + msg5b/0, msg5b/1, + msg6/0, msg6/1, + msg7/0, msg7/1, + msg9/0, msg9/1, + msg10/0, msg10/1, + msg11/0, msg11/1, + msg12/0, msg12/1, + msg13/0, msg13/1, + msg14/0, msg14/1, + msg15/0, msg15/1, + msg16/0, msg16/1, + msg17a/0, msg17a/1, + msg17b/0, msg17b/1, + msg18a/0, msg18a/1, + msg18b/0, msg18b/1, + msg18c/0, msg18c/1, + msg18d/0, msg18d/1, + msg19a/0, msg19a/1, + msg19b/0, msg19b/1, + msg20/0, msg20/1, + msg21/0, msg21/1, + msg22a/0, msg22a/1, + msg22b/0, msg22b/1, + msg23a/0, msg23a/1, + msg23b/0, msg23b/1 + + ]). + +-export([ + encoders/0, + msg_sizes/0, + coding_times/0, + encoding_times/0, + decoding_times/0, + coding_times_stat/0, + encoding_times_stat/0, + decoding_times_stat/0, + size_stat/0, + gnuplot_gif/0, + gnuplot_size_gif/0, + + gen_byte_msg/2, + gen_header_file_binary/1, + gen_ber_header/0, + gen_ber_bin_header/0, + gen_per_header/0, + single_meter/4, + count/2 + ]). + -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). -include("megaco_test_lib.hrl"). @@ -53,16 +131,20 @@ init_per_testcase(Case, Config) -> end_per_testcase(Case, Config) -> megaco_test_lib:end_per_testcase(Case, Config). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Top test case all() -> - [{group, text}, {group, binary}]. + [{group, text}, {group, binary}, {group, erl}]. groups() -> - [{text, [], [pretty, compact]}, - {flex, [], [pretty_flex, compact_flex]}, - {binary, [], [bin, ber, per]}]. + [ + {text, [], [pretty, compact]}, + {flex, [], [pretty_flex, compact_flex]}, + {binary, [], [bin, ber, per]}, + {erl, [], [standard_erl, compressed_erl]} + ]. init_per_group(_GroupName, Config) -> Config. diff --git a/lib/megaco/test/megaco_codec_test.erl b/lib/megaco/test/megaco_codec_test.erl index 007136f83e..0dfbabcc81 100644 --- a/lib/megaco/test/megaco_codec_test.erl +++ b/lib/megaco/test/megaco_codec_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -25,7 +25,18 @@ -module(megaco_codec_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + init/0 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). diff --git a/lib/megaco/test/megaco_codec_test_lib.erl b/lib/megaco/test/megaco_codec_test_lib.erl index 6eee5caaaa..7a3d4e6cf8 100644 --- a/lib/megaco/test/megaco_codec_test_lib.erl +++ b/lib/megaco/test/megaco_codec_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2019. 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. @@ -993,15 +993,15 @@ expect_exec([#expect_instruction{description = Desc, skip({What, Why}) when is_atom(What) andalso is_list(Why) -> Reason = lists:flatten(io_lib:format("~p: ~s", [What, Why])), - exit({skipped, Reason}); + ?SKIP(Reason); skip({What, Why}) -> Reason = lists:flatten(io_lib:format("~p: ~p", [What, Why])), - exit({skipped, Reason}); + ?SKIP(Reason); skip(Reason) when is_list(Reason) -> - exit({skipped, Reason}); + ?SKIP(Reason); skip(Reason1) -> Reason2 = lists:flatten(io_lib:format("~p", [Reason1])), - exit({skipped, Reason2}). + ?SKIP(Reason2). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/megaco/test/megaco_config_test.erl b/lib/megaco/test/megaco_config_test.erl index 02e06a722a..d46806927a 100644 --- a/lib/megaco/test/megaco_config_test.erl +++ b/lib/megaco/test/megaco_config_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -25,7 +25,24 @@ -module(megaco_config_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + config/1, + transaction_id_counter_mg/1, + transaction_id_counter_mgc/1, + otp_7216/1, + otp_8167/1, + otp_8183/1, + + t/0, t/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -37,6 +54,19 @@ t(Case) -> megaco_test_lib:t({?MODULE, Case}). min(M) -> timer:minutes(M). %% Test server callbacks +init_per_testcase(Case, Config) when (Case =:= otp_7216) orelse + (Case =:= otp_8167) orelse + (Case =:= otp_8183) -> + i("try starting megaco_config"), + case megaco_config:start_link() of + {ok, _} -> + C = lists:keydelete(tc_timeout, 1, Config), + do_init_per_testcase(Case, [{tc_timeout, min(3)}|C]); + {error, Reason} -> + i("Failed starting megaco_config: " + "~n ~p", [Reason]), + {skip, ?F("Failed starting config: ~p", [Reason])} + end; init_per_testcase(Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), do_init_per_testcase(Case, [{tc_timeout, min(3)}|C]). @@ -45,6 +75,12 @@ do_init_per_testcase(Case, Config) -> process_flag(trap_exit, true), megaco_test_lib:init_per_testcase(Case, Config). +end_per_testcase(Case, Config) when (Case =:= otp_7216) orelse + (Case =:= otp_8167) orelse + (Case =:= otp_8183) -> + (catch megaco_config:stop()), + process_flag(trap_exit, false), + megaco_test_lib:end_per_testcase(Case, Config); end_per_testcase(Case, Config) -> process_flag(trap_exit, false), megaco_test_lib:end_per_testcase(Case, Config). @@ -60,14 +96,17 @@ end_per_testcase(Case, Config) -> %% Top test case all() -> - [config, {group, transaction_id_counter}, - {group, tickets}]. + [ + config, + {group, transaction_id_counter}, + {group, tickets} + ]. groups() -> - [{transaction_id_counter, [], - [transaction_id_counter_mg, - transaction_id_counter_mgc]}, - {tickets, [], [otp_7216, otp_8167, otp_8183]}]. + [ + {transaction_id_counter, [], transaction_id_counter_cases()}, + {tickets, [], tickets_cases()} + ]. init_per_group(_GroupName, Config) -> Config. @@ -75,6 +114,19 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +transaction_id_counter_cases() -> + [ + transaction_id_counter_mg, + transaction_id_counter_mgc + ]. + +tickets_cases() -> + [ + otp_7216, + otp_8167, + otp_8183 + ]. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Config test case @@ -736,9 +788,6 @@ otp_7216(Config) when is_list(Config) -> put(tc, otp_7216), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, %% LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -859,9 +908,6 @@ otp_8167(Config) when is_list(Config) -> put(tc, otp8167), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -981,9 +1027,6 @@ otp_8183(Config) when is_list(Config) -> put(tc, otp8183), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -1122,28 +1165,18 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - diff --git a/lib/megaco/test/megaco_digit_map_test.erl b/lib/megaco/test/megaco_digit_map_test.erl index 998e829b67..e03d38497c 100644 --- a/lib/megaco/test/megaco_digit_map_test.erl +++ b/lib/megaco/test/megaco_digit_map_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-2019. 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. @@ -24,7 +24,27 @@ %%---------------------------------------------------------------------- -module(megaco_digit_map_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + otp_5750_01/1, + otp_5750_02/1, + otp_5799_01/1, + otp_5826_01/1, + otp_5826_02/1, + otp_5826_03/1, + otp_7449_1/1, + otp_7449_2/1, + + t/0, t/1 + ]). + -include("megaco_test_lib.hrl"). @@ -44,16 +64,18 @@ end_per_testcase(Case, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> - [{group, tickets}]. + [ + {group, tickets} + ]. groups() -> - [{tickets, [], - [{group, otp_5750}, {group, otp_5799}, - {group, otp_5826}, {group, otp_7449}]}, - {otp_5750, [], [otp_5750_01, otp_5750_02]}, - {otp_5799, [], [otp_5799_01]}, - {otp_5826, [], [otp_5826_01, otp_5826_02, otp_5826_03]}, - {otp_7449, [], [otp_7449_1, otp_7449_2]}]. + [ + {tickets, [], tickets_cases()}, + {otp_5750, [], otp_5750_cases()}, + {otp_5799, [], otp_5799_cases()}, + {otp_5826, [], otp_5826_cases()}, + {otp_7449, [], otp_7449_cases()} + ]. init_per_group(_GroupName, Config) -> Config. @@ -62,6 +84,38 @@ end_per_group(_GroupName, Config) -> Config. +tickets_cases() -> + [ + {group, otp_5750}, + {group, otp_5799}, + {group, otp_5826}, + {group, otp_7449} + ]. + +otp_5750_cases() -> + [ + otp_5750_01, + otp_5750_02 + ]. + +otp_5799_cases() -> + [ + otp_5799_01 + ]. + +otp_5826_cases() -> + [ + otp_5826_01, + otp_5826_02, + otp_5826_03 + ]. + +otp_7449_cases() -> + [ + otp_7449_1, + otp_7449_2 + ]. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/megaco/test/megaco_examples_test.erl b/lib/megaco/test/megaco_examples_test.erl index 10ca0375f6..fdf9fe29ff 100644 --- a/lib/megaco/test/megaco_examples_test.erl +++ b/lib/megaco/test/megaco_examples_test.erl @@ -25,7 +25,20 @@ -module(megaco_examples_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + simple/1, + + t/0, t/1 + ]). + -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). diff --git a/lib/megaco/test/megaco_flex_test.erl b/lib/megaco/test/megaco_flex_test.erl index 999d1abc6c..27c46a3b47 100644 --- a/lib/megaco/test/megaco_flex_test.erl +++ b/lib/megaco/test/megaco_flex_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2019. 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. @@ -219,18 +219,5 @@ p(F, A) -> TC = get(tc), io:format("*** [~s] ~p ~w ***" "~n " ++ F ++ "~n", - [formated_timestamp(), self(), TC | A]). - -formated_timestamp() -> - format_timestamp(erlang:now()). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY, MM, DD} = Date, - {Hour, Min, Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - + [?FTS(), self(), TC | A]). diff --git a/lib/megaco/test/megaco_load_test.erl b/lib/megaco/test/megaco_load_test.erl index 511e5a2e8e..9cce9e70ce 100644 --- a/lib/megaco/test/megaco_load_test.erl +++ b/lib/megaco/test/megaco_load_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -24,7 +24,33 @@ %%---------------------------------------------------------------------- -module(megaco_load_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + single_user_light_load/1, + single_user_medium_load/1, + single_user_heavy_load/1, + single_user_extreme_load/1, + + multi_user_light_load/1, + multi_user_medium_load/1, + multi_user_heavy_load/1, + multi_user_extreme_load/1, + + t/0, t/1 + ]). + +-export([ + do_multi_load/3, + multi_load_collector/7 + ]). + -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -66,8 +92,6 @@ t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). -min(M) -> timer:minutes(M). - %% Test server callbacks init_per_testcase(single_user_light_load = Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), @@ -108,15 +132,33 @@ end_per_testcase(Case, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> - [single_user_light_load, - single_user_medium_load, single_user_heavy_load, - single_user_extreme_load, multi_user_light_load, - multi_user_medium_load, multi_user_heavy_load, - multi_user_extreme_load]. + [ + {group, single}, + {group, multi} + ]. groups() -> - []. - + [ + {single, [], single_cases()}, + {multi, [], multi_cases()} + ]. + +single_cases() -> + [ + single_user_light_load, + single_user_medium_load, + single_user_heavy_load, + single_user_extreme_load + ]. + +multi_cases() -> + [ + multi_user_light_load, + multi_user_medium_load, + multi_user_heavy_load, + multi_user_extreme_load + ]. + init_per_group(_GroupName, Config) -> Config. @@ -326,6 +368,17 @@ load_controller(Config, Fun) when is_list(Config) and is_function(Fun) -> d("load_controller -> " "loader [~p] terminated with ok~n", [Loader]), ok; + {'EXIT', Loader, {skipped, {fatal, Reason, File, Line}}} -> + i("load_controller -> " + "loader [~p] terminated with fatal skip" + "~n Reason: ~p" + "~n At: ~p:~p", [Loader, Reason, File, Line]), + ?SKIP(Reason); + {'EXIT', Loader, {skipped, Reason}} -> + i("load_controller -> " + "loader [~p] terminated with skip" + "~n Reason: ~p", [Loader, Reason]), + ?SKIP(Reason); {'EXIT', Loader, Reason} -> i("load_controller -> " "loader [~p] terminated with" @@ -629,14 +682,6 @@ make_mids([MgNode|MgNodes], Mids) -> exit("Test node must be started with '-sname'") end. -tim() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). - -sleep(X) -> receive after X -> ok end. - -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - maybe_display_system_info(NumLoaders) when NumLoaders > 50 -> [{display_system_info, timer:seconds(2)}]; maybe_display_system_info(NumLoaders) when NumLoaders > 10 -> @@ -647,50 +692,32 @@ maybe_display_system_info(_) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +min(M) -> timer:minutes(M). + i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_mess_test.erl b/lib/megaco/test/megaco_mess_test.erl index d3216c2a6d..3fd39a9e58 100644 --- a/lib/megaco/test/megaco_mess_test.erl +++ b/lib/megaco/test/megaco_mess_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2016. All Rights Reserved. +%% Copyright Ericsson AB 1999-2019. 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. @@ -281,6 +281,8 @@ -define(VERSION, 1). +-define(USER_MOD, megaco_mess_user_test). + -define(TEST_VERBOSITY, debug). -define(MGC_VERBOSITY, debug). -define(MG_VERBOSITY, debug). @@ -303,26 +305,48 @@ -define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). -define(SEND(Expr), - ?VERIFY(ok, megaco_mess_user_test:apply_proxy(fun() -> Expr end))). + ?VERIFY(ok, ?USER_MOD:apply_proxy(fun() -> Expr end))). -define(USER(Expected, Reply), - megaco_mess_user_test:reply(?MODULE, - ?LINE, - fun(Actual) -> - case ?VERIFY(Expected, Actual) of - Expected -> {ok, Reply}; - UnExpected -> {error, {reply_verify, - ?MODULE, - ?LINE, - UnExpected}} - end - end)). - -%% t() -> megaco_test_lib:t(?MODULE). -%% t(Case) -> megaco_test_lib:t({?MODULE, Case}). - - -min(M) -> timer:minutes(M). + ?USER_MOD:reply(?MODULE, + ?LINE, + fun(Actual) -> + case ?VERIFY(Expected, Actual) of + Expected -> {ok, Reply}; + UnExpected -> {error, {reply_verify, + ?MODULE, + ?LINE, + UnExpected}} + end + end)). + +%% Some generator (utility) macros +-define(GM_START(), megaco_start). +-define(GM_STOP(), megaco_stop). +-define(GM_START_USER(M, RI, C), {megaco_start_user, M, RI, C}). +-define(GM_START_USER(M, RI), ?GM_START_USER(M, RI, [])). +-define(GM_STOP_USER(), megaco_stop_user). +-define(GMSI(I), {megaco_system_info, I}). +-define(GMSI_USERS(), ?GMSI(users)). +-define(GMSI_CONNS(), ?GMSI(connections)). +-define(GMCAST(Reqs, Opts), {megaco_cast, Reqs, Opts}). +-define(GMCAST(Reqs), ?GMCAST(Reqs, [])). +-define(GMCB(CB, VF), {megaco_callback, CB, VF}). +-define(GMCB_CONNECT(VF), ?GMCB(handle_connect, VF)). +-define(GMCB_TRANS_REP(VF), ?GMCB(handle_trans_reply, VF)). +-define(GMT(T), {megaco_trace, T}). +-define(GMT_ENABLE(), ?GMT(enable)). +-define(GMT_DISABLE(), ?GMT(disable)). +-define(GD(D), {debug, D}). +-define(GD_ENABLE(), ?GD(true)). +-define(GD_DISABLE(), ?GD(false)). +-define(GS(T), {sleep, T}). + +-define(GSND(T, D), {send, T, D}). +-define(GERCV(T, VF, TO), {expect_receive, T, {VF, TO}}). + + +min(M) -> ?MINS(M). %% Test server callbacks init_per_testcase(otp_7189 = Case, Config) -> @@ -396,8 +420,19 @@ groups() -> init_per_suite(Config) -> io:format("~w:init_per_suite -> entry with" - "~n Config: ~p" - "~n", [?MODULE, Config]), + "~n Config: ~p" + "~n OS Type: ~p" + "~n OS Version: ~s" + "~n", + [?MODULE, + Config, + os:type(), + case os:version() of + {Major, Minor, Release} -> + ?F("~w.~w.~w", [Major, Minor, Release]); + Str when is_list(Str) -> + Str + end]), Config. end_per_suite(_Config) -> @@ -491,12 +526,12 @@ request_and_reply_plain(suite) -> request_and_reply_plain(Config) when is_list(Config) -> ?ACQUIRE_NODES(1, Config), d("request_and_reply_plain -> start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("request_and_reply_plain -> start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -564,6 +599,7 @@ request_and_reply_plain(Config) when is_list(Config) -> ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% OTP-4760 @@ -1715,9 +1751,6 @@ rarpaop_mg_event_sequence(Port, EncMod, EncConf) -> ScrVerifyFun = ?rarpaop_mg_verify_service_change_rep_msg_fun(), PendVerifyFun = ?rarpaop_mg_verify_pending_msg_fun(TransId), NrVerifyFun = ?rarpaop_mg_verify_notify_rep_msg_fun(TransId, TermId), -%% ScrVerifyFun = rarpaop_mg_verify_service_change_rep_msg_fun(), -%% PendVerifyFun = rarpaop_mg_verify_pending_msg_fun(TransId), -%% NrVerifyFun = rarpaop_mg_verify_notify_rep_msg_fun(TransId, TermId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -2359,9 +2392,6 @@ strar_mg_event_sequence(text, tcp) -> ConnectVerify = ?strar_mg_verify_handle_connect_fun(), ServiceChangeReplyVerify = ?strar_mg_verify_service_change_reply_fun(), NotifyReplyVerify = ?strar_mg_verify_notify_reply_fun(), -%% ConnectVerify = strar_mg_verify_handle_connect_fun(), -%% ServiceChangeReplyVerify = strar_mg_verify_service_change_reply_fun(), -%% NotifyReplyVerify = fun strar_mg_verify_notify_reply/1, EvSeq = [ {debug, true}, megaco_start, @@ -3437,9 +3467,6 @@ raraa_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?raraa_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?raraa_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = raraa_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = raraa_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -4048,9 +4075,6 @@ rarana_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?rarana_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?rarana_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = rarana_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = rarana_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -4663,9 +4687,6 @@ rarala_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?rarala_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?rarala_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = rarala_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = rarala_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -5298,15 +5319,6 @@ trarar_mg_event_sequence(text, tcp) -> ?trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 3, 3), NrVerifyFun4 = ?trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 4, 4), -%% ScrVerifyFun = trarar_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun1 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 1, 1), -%% NrVerifyFun2 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 2, 2), -%% NrVerifyFun3 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 3, 3), -%% NrVerifyFun4 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 4, 4), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -5983,11 +5995,6 @@ pap_mg_event_sequence(text, tcp) -> ?pap_mg_verify_pending_msg_fun(TransId), NrVerifyFun = ?pap_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = pap_mg_verify_service_change_rep_msg_fun(), -%% PendingVerifyFun = -%% pap_mg_verify_pending_msg_fun(TransId), -%% NrVerifyFun = -%% pap_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -6380,10 +6387,6 @@ rapalr_mgc_event_sequence(text, tcp) -> NrVerifyFun = ?rapalr_mgc_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), AckVerifyFun = ?rapalr_mgc_verify_trans_ack_msg_fun(TransId), -%% ScrVerifyFun = rapalr_mgc_verify_service_change_req_msg_fun(), -%% NrVerifyFun = -%% rapalr_mgc_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), -%% AckVerifyFun = rapalr_mgc_verify_trans_ack_msg_fun(TransId), EvSeq = [{debug, false}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -6866,12 +6869,12 @@ dist(Config) when is_list(Config) -> ?SKIP("Needs a re-write..."), [_Local, Dist] = ?ACQUIRE_NODES(2, Config), d("dist -> start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("dist -> start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -6977,7 +6980,7 @@ dist(Config) when is_list(Config) -> ?RECEIVE([]), d("dist -> stop proxy",[]), - megaco_mess_user_test:stop_proxy(), + ?USER_MOD:stop_proxy(), d("dist -> done", []), ok. @@ -7444,9 +7447,6 @@ otp_5805_mg_event_sequence(text, tcp) -> ?otp_5805_mg_verify_service_change_rep_msg_fun(), EDVerify = ?otp_5805_mg_verify_error_descriptor_msg_fun(), -%% ServiceChangeReplyVerifyFun = -%% otp_5805_mg_verify_service_change_rep_msg_fun(), -%% EDVerify = otp_5805_mg_verify_error_descriptor_msg_fun(), MgEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -7939,8 +7939,6 @@ otp_5881_mgc_event_sequence(text, tcp) -> %% Pending = otp_5881_pending_msg(Mid,2), ServiceChangeVerifyFun = ?otp_5881_mgc_verify_service_change_req_msg_fun(), NotifyReqVerifyFun = ?otp_5881_mgc_verify_notify_req_msg_fun(), -%% ServiceChangeVerifyFun = otp_5881_verify_service_change_req_msg_fun(), -%% NotifyReqVerifyFun = otp_5881_verify_notify_request_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -8211,8 +8209,6 @@ otp_5887_mgc_event_sequence(text, tcp) -> NotifyReply = otp_5887_notify_reply_msg(Mid, 2, 0, TermId), ServiceChangeVerifyFun = ?otp_5887_mgc_verify_service_change_req_msg_fun(), NotifyReqVerifyFun = ?otp_5887_mgc_verify_notify_req_msg_fun(), -%% ServiceChangeVerifyFun = otp_5887_verify_service_change_req_msg_fun(), -%% NotifyReqVerifyFun = otp_5887_verify_notify_request_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -8362,7 +8358,7 @@ otp_6253(Config) when is_list(Config) -> MgMid = ipv4_mid(4711), ?VERIFY(ok, application:start(megaco)), - ?VERIFY(ok, megaco:start_user(MgMid, [{send_mod, megaco_mess_user_test}, + ?VERIFY(ok, megaco:start_user(MgMid, [{send_mod, ?USER_MOD}, {request_timer, infinity}, {reply_timer, infinity}])), @@ -8668,8 +8664,6 @@ otp_6275_mgc_event_sequence(text, tcp) -> NotifyReq = otp_6275_mgc_notify_request_msg(Mid, 2, 1, TermId, 1), SCRVerifyFun = ?otp_6275_mgc_verify_service_change_req_msg_fun(), NotifyReplyVerifyFun = ?otp_6275_mgc_verify_notify_rep_msg_fun(), -%% SCRVerifyFun = otp_6275_mgc_verify_service_change_req_fun(), -%% NotifyReplyVerifyFun = otp_6275_mgc_verify_notify_reply_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, @@ -11015,12 +11009,12 @@ otp_6865_request_and_reply_plain_extra1(Config) when is_list(Config) -> ok = megaco_tc_controller:insert(extra_transport_info, ExtraInfo), d("start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -12286,15 +12280,15 @@ otp_7189_mg_event_sequence(text, tcp) -> ?otp_7189_mg_verify_service_change_rep_msg_fun(), NotifyReqVerify = ?otp_7189_mg_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), EvSeq = [ - {debug, true}, + ?GD_ENABLE(), {decode, DecodeFun}, {encode, EncodeFun}, {connect, 2944}, - {send, "service-change-request", ServiceChangeReq}, - {expect_receive, "service-change-reply", {ServiceChangeReplyVerifyFun, 2000}}, - {expect_receive, "notify request", {NotifyReqVerify, 2000}}, - {sleep, 100}, - {send, "pending", Pending}, + ?GSND("service-change-request", ServiceChangeReq), + ?GERCV("service-change-reply", ServiceChangeReplyVerifyFun, ?SECS(5)), + ?GERCV("notify request", NotifyReqVerify, ?SECS(5)), + ?GS(100), + ?GSND("pending", Pending), {expect_closed, timer:seconds(120)}, disconnect ], @@ -12532,7 +12526,7 @@ otp_7259(Config) when is_list(Config) -> megaco_test_generic_transport:incomming_message(Pid, NotifyReply), d("[MG] await the generator reply"), - await_completion([MgId], 5000), + await_completion([MgId], 7000), %% Tell Mg to stop i("[MG] stop generator"), @@ -12832,13 +12826,13 @@ otp_7713(Config) when is_list(Config) -> i("starting"), d("start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), Extra = otp7713_extra, PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -12954,10 +12948,11 @@ otp_8183_request1(Config) when is_list(Config) -> i("wait some before issuing the notify reply (twice)"), sleep(500), - i("send the notify reply, twice times"), + i("send the notify reply - twice"), NotifyReply = otp_8183_r1_mgc_notify_reply_msg(MgcMid, TransId2, Cid2, TermId2), megaco_test_generic_transport:incomming_message(Pid, NotifyReply), + sleep(100), %% This is to "make sure" the events come in the "right" order megaco_test_generic_transport:incomming_message(Pid, NotifyReply), d("await the generator reply"), @@ -13228,13 +13223,33 @@ otp_8183_r1_mg_verify_notify_rep_fun(Nr) -> end. -endif. -otp_8183_r1_mg_verify_notify_rep(Nr, +otp_8183_r1_mg_verify_notify_rep( + Nr, {handle_trans_reply, _CH, ?VERSION, {ok, Nr, [AR]}, _}) -> io:format("otp_8183_r1_mg_verify_notify_rep -> ok" "~n Nr: ~p" "~n AR: ~p" "~n", [Nr, AR]), {ok, AR, ok}; +otp_8183_r1_mg_verify_notify_rep( + ExpNr, + {handle_trans_reply, _CH, ?VERSION, {ok, ActNr, [AR]}, _}) -> + io:format("otp_8183_r1_mg_verify_notify_rep -> error" + "~n Expected Nr: ~p" + "~n Actual Nr: ~p" + "~n AR: ~p" + "~n", [ExpNr, ActNr, AR]), + Error = {unexpected_nr, ExpNr, ActNr}, + {error, Error, ok}; +otp_8183_r1_mg_verify_notify_rep( + Nr, + {handle_trans_reply, _CH, ?VERSION, Res, _}) -> + io:format("otp_8183_r1_mg_verify_notify_rep -> error" + "~n Nr: ~p" + "~n Res: ~p" + "~n", [Nr, Res]), + Error = {unexpected_result, Nr, Res}, + {error, Error, ok}; otp_8183_r1_mg_verify_notify_rep(Nr, Else) -> io:format("otp_8183_r1_mg_verify_notify_rep -> unknown" "~n Nr: ~p" diff --git a/lib/megaco/test/megaco_mess_user_test.erl b/lib/megaco/test/megaco_mess_user_test.erl index b5a554112e..d2a9c0f290 100644 --- a/lib/megaco/test/megaco_mess_user_test.erl +++ b/lib/megaco/test/megaco_mess_user_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -86,13 +86,13 @@ reply(Mod, Line, Fun) when is_function(Fun) -> {?MODULE, Pid, UserCallback} -> UserReply = Fun(UserCallback), Pid ! {?MODULE, self(), UserReply}, - UserReply; - Other -> - megaco_test_lib:error(Other, Mod, Line), - {error, Other} -%% after 1000 -> -%% megaco_test_lib:error(timeout, Mod, Line), -%% {error, timeout} + UserReply%% ; + %% Other -> + %% megaco_test_lib:error(Other, Mod, Line), + %% {error, Other} + after 10000 -> + megaco_test_lib:error(timeout, Mod, Line), + {error, timeout} end. call(UserCallback) -> diff --git a/lib/megaco/test/megaco_mib_test.erl b/lib/megaco/test/megaco_mib_test.erl index 06ac7c08ed..9d1d408f18 100644 --- a/lib/megaco/test/megaco_mib_test.erl +++ b/lib/megaco/test/megaco_mib_test.erl @@ -264,7 +264,7 @@ connect(Config) when is_list(Config) -> d("connect -> (Mg1) service change result: ~p", [Res1]), %% Collect the statistics - progress("collect MG1 statustics (after service change)"), + progress("collect MG1 statistics (after service change)"), {ok, Mg1Stats1} = get_stats(Mg1, 1), d("connect -> stats for Mg1: ~n~p", [Mg1Stats1]), progress("collect MGC statistics (after MG1 service change)"), @@ -279,7 +279,7 @@ connect(Config) when is_list(Config) -> d("connect -> (Mg2) service change result: ~p", [Res2]), %% Collect the statistics - progress("collect MG2 statustics (after service change)"), + progress("collect MG2 statistics (after service change)"), {ok, Mg2Stats1} = get_stats(Mg2, 1), d("connect -> stats for Mg1: ~n~p", [Mg2Stats1]), progress("collect MGC statistics (after MG2 service change)"), @@ -751,7 +751,7 @@ mgc_init(Config) -> d("mgc_init -> entry"), Mid = get_conf(local_mid, Config), RI = get_conf(receive_info, Config), - d("mgc_init -> start megaco"), + i("mgc_init -> start megaco"), application:start(megaco), d("mgc_init -> start megaco user"), megaco:start_user(Mid, []), @@ -763,7 +763,7 @@ mgc_init(Config) -> RH = megaco:user_info(Mid,receive_handle), d("mgc_init -> parse receive info"), ListenTo = mgc_parse_receive_info(RI, RH), - d("mgc_init -> start transports"), + i("mgc_init -> start transport(s)"), {Tcp, Udp} = mgc_start_transports(ListenTo), {Mid, Tcp, Udp}. @@ -948,7 +948,7 @@ mgc_start_transports([{_Port, RH}|_ListenTo], _TcpSup, _UdpSup) -> mgc_start_tcp(RH, Port, undefined) -> - d("start tcp transport"), + i("start tcp transport"), case megaco_tcp:start_transport() of {ok, Sup} -> mgc_start_tcp(RH, Port, Sup); @@ -956,7 +956,7 @@ mgc_start_tcp(RH, Port, undefined) -> throw({error, {failed_starting_tcp_transport, Else}}) end; mgc_start_tcp(RH, Port, Sup) when is_pid(Sup) -> - d("tcp listen on ~p", [Port]), + i("tcp listen on ~p", [Port]), Opts = [{port, Port}, {receive_handle, RH}, {tcp_options, [{nodelay, true}]}], @@ -986,7 +986,7 @@ mgc_tcp_create_listen(Sup, Opts, MaxN, N, _InitialReason) mgc_start_udp(RH, Port, undefined) -> - d("start udp transport"), + i("start udp transport"), case megaco_udp:start_transport() of {ok, Sup} -> mgc_start_udp(RH, Port, Sup); @@ -994,7 +994,7 @@ mgc_start_udp(RH, Port, undefined) -> throw({error, {failed_starting_udp_transport, Else}}) end; mgc_start_udp(RH, Port, Sup) -> - d("open udp ~p", [Port]), + i("open udp ~p", [Port]), Opts = [{port, Port}, {receive_handle, RH}], case megaco_udp:open(Sup, Opts) of {ok, _SendHandle, _ControlPid} -> @@ -1119,7 +1119,7 @@ mg_init(Config) -> d("mg_init -> entry"), Mid = get_conf(local_mid, Config), RI = get_conf(receive_info, Config), - d("mg_init -> start megaco"), + i("mg_init -> start megaco"), application:start(megaco), d("mg_init -> start megaco user"), megaco:start_user(Mid, []), @@ -1131,7 +1131,7 @@ mg_init(Config) -> RH = megaco:user_info(Mid,receive_handle), d("mg_init -> parse receive info"), {MgcPort,RH1} = mg_parse_receive_info(RI, RH), - d("mg_init -> start transport with"), + i("mg_init -> start transport(s)"), ConnHandle = mg_start_transport(MgcPort, RH1), {Mid, ConnHandle}. @@ -1255,16 +1255,6 @@ mg_close_conn(CH) -> SendMod -> exit(Pid, Reason) end. -% display_tuple(T) when tuple(T), size(T) > 0 -> -% i("size(T): ~p", [size(T)]), -% display_tuple(T,1). - -% display_tuple(T,P) when P > size(T) -> -% ok; -% display_tuple(T,P) -> -% i("T[~p]: ~p", [P,element(P,T)]), -% display_tuple(T,P+1). - mg_parse_receive_info(RI, RH) -> d("mg_parse_receive_info -> get encoding module"), @@ -1292,7 +1282,7 @@ mg_start_transport(_, #megaco_receive_handle{send_mod = Mod}) -> mg_start_tcp(MgcPort, RH) -> - d("start tcp transport"), + i("start tcp transport"), case megaco_tcp:start_transport() of {ok, Sup} -> {ok, LocalHost} = inet:gethostname(), @@ -1316,7 +1306,7 @@ mg_start_tcp(MgcPort, RH) -> mg_start_udp(MgcPort, RH) -> - d("start udp transport"), + i("start udp transport"), case megaco_udp:start_transport() of {ok, Sup} -> %% Some linux (Ubuntu) has "crap" in their /etc/hosts, that @@ -1723,6 +1713,7 @@ progress(F) -> progress(F, []). progress(F, A) -> + io:format("~s " ++ F ++ "~n", [?FTS()|A]), io:format(user, "~s " ++ F ++ "~n", [?FTS()|A]). diff --git a/lib/megaco/test/megaco_mreq_test.erl b/lib/megaco/test/megaco_mreq_test.erl index e6a5ed3181..ba20329ab3 100644 --- a/lib/megaco/test/megaco_mreq_test.erl +++ b/lib/megaco/test/megaco_mreq_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -24,7 +24,21 @@ %%---------------------------------------------------------------------- -module(megaco_mreq_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + req_and_rep/1, + req_and_pending/1, + req_and_cancel/1, + + t/0, t/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -51,16 +65,19 @@ -define(MG_START(Pid, Mid, Enc, Transp, Verb), megaco_test_mg:start(Pid, Mid, Enc, Transp, Verb)). --define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). --define(MG_GET_STATS(Pid, No), megaco_test_mg:get_stats(Pid, No)). --define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). --define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). --define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). --define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). --define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). --define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). +-define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). +-define(MG_GET_STATS(Pid), megaco_test_mg:get_stats(Pid)). +-define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). +-define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). +-define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). +-define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). +-define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). +-define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). -define(MG_APPLY_LOAD(Pid,CntStart), megaco_test_mg:apply_load(Pid,CntStart)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). @@ -74,6 +91,7 @@ end_per_testcase(Case, Config) -> process_flag(trap_exit, false), megaco_test_lib:end_per_testcase(Case, Config). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> @@ -228,7 +246,7 @@ req_and_rep_stop_mg(MGs) -> req_and_rep_get_mg_stats([], Acc) -> lists:reverse(Acc); req_and_rep_get_mg_stats([{Name, Pid}|Mgs], Acc) -> - {ok, Stats} = ?MG_GET_STATS(Pid, 1), + {ok, Stats} = ?MG_GET_STATS(Pid), d("req_and_rep_get_mg_stats -> stats for ~s: ~n~p~n", [Name, Stats]), req_and_rep_get_mg_stats(Mgs, [{Name, Stats}|Acc]). @@ -399,11 +417,13 @@ req_and_cancel(Config) when is_list(Config) -> req_and_cancel_analyze_result({ok,{_PV,Res}}) -> - d("req_and_cancel -> notify request result: ~n ~p", [Res]), + i("req_and_cancel -> notify request result: ~n ~p", [Res]), req_and_cancel_analyze_result2(Res); req_and_cancel_analyze_result(Unexpected) -> exit({unexpected_result,Unexpected}). +req_and_cancel_analyze_result2({error,{user_cancel,req_and_cancel}}) -> + ok; req_and_cancel_analyze_result2([]) -> ok; req_and_cancel_analyze_result2([{error,{user_cancel,req_and_cancel}}|Res]) -> @@ -429,9 +449,6 @@ sleep(X) -> receive after X -> ok end. -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i(F) -> @@ -441,8 +458,8 @@ i(F, A) -> print(info, get(verbosity), "", F, A). -d(F) -> - d(F, []). +%% d(F) -> +%% d(F, []). d(F, A) -> print(debug, get(verbosity), "DBG: ", F, A). @@ -456,20 +473,9 @@ print(Severity, Verbosity, P, F, A) -> print(printable(Severity,Verbosity), P, F, A). print(true, P, F, A) -> - io:format("~s~p:~s: " ++ F ++ "~n", [P, self(), get(sname) | A]); + io:format("*** [~s] ~s ~p ~s ***" + "~n " ++ F ++ "~n", + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - diff --git a/lib/megaco/test/megaco_pending_limit_test.erl b/lib/megaco/test/megaco_pending_limit_test.erl index ef3b7e51cf..e0c0468d99 100644 --- a/lib/megaco/test/megaco_pending_limit_test.erl +++ b/lib/megaco/test/megaco_pending_limit_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -2065,23 +2065,12 @@ await_completion(Ids) -> ?ERROR({failed, Reply}) end. -%% await_completion(Ids, Timeout) -> -%% case megaco_test_generator_lib:await_completion(Ids, Timeout) of -%% {ok, Reply} -> -%% d("OK => Reply: ~n~p", [Reply]), -%% ok; -%% {error, Reply} -> -%% d("ERROR => Reply: ~n~p", [Reply]), -%% ?ERROR({failed, Reply}) -%% end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sleep(X) -> receive after X -> ok end. -%% error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2089,49 +2078,30 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -%% print(F, A) -> -%% io:format("*** [~s] ***" -%% "~n " ++ F ++ "~n", -%% [format_timestamp(now()) | A]). - -format_timestamp(Now) -> megaco:format_timestamp(Now). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% random_init() -> -%% {A,B,C} = now(), -%% random:seed(A,B,C). - -%% random() -> -%% 10 * random:uniform(50). - -%% apply_load_timer() -> -%% erlang:send_after(random(), self(), apply_load_timeout). - - - diff --git a/lib/megaco/test/megaco_segment_test.erl b/lib/megaco/test/megaco_segment_test.erl index ddb8b9f06b..47f708a14b 100644 --- a/lib/megaco/test/megaco_segment_test.erl +++ b/lib/megaco/test/megaco_segment_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-2019. 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. @@ -7718,29 +7718,9 @@ await_completion(Ids) -> ?ERROR({failed, Reply}) end. -%% await_completion(Ids, Timeout) -> -%% case megaco_test_generator_lib:await_completion(Ids, Timeout) of -%% {ok, Reply} -> -%% d("OK => Reply: ~n~p", [Reply]), -%% ok; -%% {error, {OK, ERROR}} -> -%% d("ERROR => " -%% "~n OK: ~p" -%% "~n ERROR: ~p", [OK, ERROR]), -%% ?ERROR({failed, ERROR}); -%% {error, Reply} -> -%% d("ERROR => Reply: ~n~p", [Reply]), -%% ?ERROR({failed, Reply}) -%% end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% tim() -> -%% {A,B,C} = erlang:now(), -%% A*1000000000+B*1000+(C div 1000). - - make_node_name(Name) -> case string:tokens(atom_to_list(node()), [$@]) of [_,Host] -> @@ -7754,8 +7734,6 @@ make_node_name(Name) -> sleep(X) -> receive after X -> ok end. -%% error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -7763,44 +7741,31 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity, Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -format_timestamp(Now) -> megaco:format_timestamp(Now). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% random_init() -> -%% {A,B,C} = now(), -%% random:seed(A,B,C). - -%% random() -> -%% 10 * random:uniform(50). - -%% apply_load_timer() -> -%% erlang:send_after(random(), self(), apply_load_timeout). - - diff --git a/lib/megaco/test/megaco_tcp_test.erl b/lib/megaco/test/megaco_tcp_test.erl index a1865ad690..cc66a40f43 100644 --- a/lib/megaco/test/megaco_tcp_test.erl +++ b/lib/megaco/test/megaco_tcp_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -117,15 +117,39 @@ end_per_testcase(Case, Config) -> %% Test case definitions %%====================================================================== all() -> - [{group, start}, {group, sending}, {group, errors}]. + [ + {group, start}, + {group, sending}, + {group, errors} + ]. groups() -> - [{start, [], - [start_normal, start_invalid_opt, start_and_stop]}, - {sending, [], [sendreceive, block_unblock]}, - {errors, [], - [socket_failure, accept_process, accept_supervisor, - connection_supervisor, tcp_server]}]. + [{start, [], start_cases()}, + {sending, [], sending_cases()}, + {errors, [], errors_cases()}]. + +start_cases() -> + [ + start_normal, + start_invalid_opt, + start_and_stop + ]. + +sending_cases() -> + [ + sendreceive, + block_unblock + ]. + +errors_cases() -> + [ + socket_failure, + accept_process, + accept_supervisor, + connection_supervisor, + tcp_server + ]. + init_per_group(_GroupName, Config) -> Config. @@ -196,17 +220,8 @@ start_and_stop(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, - + await_server_listening(Server, Client), + await_command_handler_completion([Server, Client], timer:seconds(20)), p("done"), ok. @@ -335,16 +350,7 @@ sendreceive(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, + await_server_listening(Server, Client), await_command_handler_completion([Server, Client], timer:seconds(20)), p("done"), @@ -544,27 +550,9 @@ block_unblock(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, - - ok = - receive - {blocked, Client} -> - p("received blocked message from client [~p] => " - "send continue to server [~p]~n", [Client, Server]), - Server ! {continue, self()}, - ok - after 5000 -> - {error, timeout} - end, + await_server_listening(Server, Client), + + await_client_blocked(Server, Client), await_command_handler_completion([Server, Client], timer:seconds(30)), p("done"), @@ -908,6 +896,42 @@ process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) %% Internal functions %%====================================================================== +await_server_listening(Server, Client) -> + receive + {listening, Server} -> + p("received listening message from server [~p] => " + "send continue to client [~p]" + "~n", [Server, Client]), + Client ! {continue, self()}, + ok + after 5000 -> + %% There is no normal reason why this should take any time. + %% Normally, this takes a few milli seconds. So, if we are not + %% up and running after 5 seconds, we give up and skip!! + exit(Server, kill), + exit(Client, kill), + ?SKIP("Server timeout (listen)") + end. + + +await_client_blocked(Server, Client) -> + receive + {blocked, Client} -> + p("received blocked message from client [~p] => " + "send continue to server [~p]~n", [Client, Server]), + Server ! {continue, self()}, + ok + after 5000 -> + %% There is no normal reason why this should take any time. + %% Normally, this takes a few milli seconds. So, if we are not + %% up and running after 5 seconds, we give up and skip!! + exit(Client, kill), + exit(Server, kill), + ?SKIP("Client timeout (blocked)") + end. + + + %% ------- Server command handler and utility functions ---------- server_start_command_handler(Node, Commands) -> @@ -1214,23 +1238,14 @@ p(F, A) -> p(S, F, A) when is_list(S) -> io:format("*** [~s] ~p ~s ***" "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), S | A]); + [?FTS(), self(), S | A]); p(_S, F, A) -> io:format("*** [~s] ~p ~s *** " "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), "undefined" | A]). + [?FTS(), self(), "undefined" | A]). ms() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). - + erlang:monotonic_time(milli_seconds). + -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_test_deliver.erl b/lib/megaco/test/megaco_test_deliver.erl index 78033f0e36..c042d9c9a7 100644 --- a/lib/megaco/test/megaco_test_deliver.erl +++ b/lib/megaco/test/megaco_test_deliver.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -186,4 +186,4 @@ i(F) -> i(F, []). i(F, A) -> - io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]). + io:format("*** [~s] ~p ~w:" ++ F ++ "~n", [?FTS(), self(), ?MODULE | A]). diff --git a/lib/megaco/test/megaco_test_generator.erl b/lib/megaco/test/megaco_test_generator.erl index 63f66bda07..8ea7f5ddf7 100644 --- a/lib/megaco/test/megaco_test_generator.erl +++ b/lib/megaco/test/megaco_test_generator.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -47,7 +47,6 @@ print/3, print/4 ]). --export([behaviour_info/1]). %% Internal exports -export([start/4]). @@ -65,6 +64,7 @@ -include_lib("megaco/include/megaco.hrl"). +-include("megaco_test_lib.hrl"). %%---------------------------------------------------------------------- @@ -90,15 +90,32 @@ %%% API %%%========================================================================= -behaviour_info(callbacks) -> - [ - {init, 1}, - {handle_parse, 2}, - {handle_exec, 2}, - {terminate, 2} - ]; -behaviour_info(_Other) -> - undefined. +-callback init(Args) -> {ok, State} | {error, Reason} when + Args :: term(), + State :: term(), + Reason :: term(). + +-callback handle_parse(Instruction, State) -> + {ok, NewInstruction, NewState} | + {error, Reason} when + Instruction :: term(), + State :: term(), + NewInstruction :: term(), + NewState :: term(), + Reason :: term(). + +-callback handle_exec(Instruction, State) -> + {ok, NewState} | + {error, Reason} when + Instruction :: term(), + State :: term(), + NewState :: term(), + Reason :: term(). + +-callback terminate(Reason, State) -> + megaco:void() when + Reason :: term(), + State :: term(). %%---------------------------------------------------------------------- @@ -533,22 +550,13 @@ print(P, F, A) -> print([], undefined, F, A) -> io:format("*** [~s] ~p *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self()|A]); + [?FTS(), self() | A]); print(P, undefined, F, A) -> io:format("*** [~s] ~p ~s *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self(),P|A]); + [?FTS(), self(), P | A]); print(P, N, F, A) -> io:format("*** [~s] ~p ~s~s *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self(),N,P|A]). - + [?FTS(), self(), N, P | A]). -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY, MM, DD} = Date, - {Hour, Min, Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY, MM, DD, Hour, Min, Sec, round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_test_generic_transport.erl b/lib/megaco/test/megaco_test_generic_transport.erl index 3185e4c6b6..cd387f748a 100644 --- a/lib/megaco/test/megaco_test_generic_transport.erl +++ b/lib/megaco/test/megaco_test_generic_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -59,7 +59,7 @@ receive_handle}). -include_lib("megaco/include/megaco.hrl"). -%% -include("megaco_test_lib.hrl"). +-include("megaco_test_lib.hrl"). -define(SERVER, ?MODULE). @@ -335,21 +335,11 @@ d(F) -> d(F, []). d(F, A) -> - print(now(), F, A). + print(F, A). -print(Ts, F, A) -> +print(F, A) -> io:format("*** [~s] GENERIC TRANSPORT [~p] ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), self() | A]). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - + [?FTS(), self() | A]). diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl index 0617b96456..7c8c287e7c 100644 --- a/lib/megaco/test/megaco_test_lib.erl +++ b/lib/megaco/test/megaco_test_lib.erl @@ -318,9 +318,9 @@ t({Mod, {group, Name} = Group, Groups}, Config) [Name, Error]), [{failed, {Mod, Group}, Error}] catch - exit:{skipped, SkipReason} -> + exit:{skip, SkipReason} -> io:format(" => skipping group: ~p~n", [SkipReason]), - [{skipped, {Mod, Group}, SkipReason, 0}]; + [{skip, {Mod, Group}, SkipReason, 0}]; error:undef -> [t({Mod, Case, Groups}, Config) || Case <- GroupsAndCases]; @@ -383,9 +383,9 @@ t(Mod, Config) when is_atom(Mod) -> io:format(" => suite init failed: ~p~n", [Error]), [{failed, {Mod, init_per_suite}, Error}] catch - exit:{skipped, SkipReason} -> + exit:{skip, SkipReason} -> io:format(" => skipping suite: ~p~n", [SkipReason]), - [{skipped, {Mod, init_per_suite}, SkipReason, 0}]; + [{skip, {Mod, init_per_suite}, SkipReason, 0}]; error:undef -> [t({Mod, Case, Groups}, Config) || Case <- Cases]; T:E -> @@ -458,10 +458,10 @@ wait_for_evaluator(Pid, Mod, Fun, Config, Errors, AccTime) -> megaco:report_event(20, Mod, ?MODULE, Label ++ " failed", [TestCase, Config, {return, Fail}, Errors]), {failed, {Mod,Fun}, Fail, Time}; - {'EXIT', Pid, {skipped, Reason}, Time} -> + {'EXIT', Pid, {skip, Reason}, Time} -> megaco:report_event(20, Mod, ?MODULE, Label ++ " skipped", - [TestCase, Config, {skipped, Reason}]), - {skipped, {Mod, Fun}, Errors, Time}; + [TestCase, Config, {skip, Reason}]), + {skip, {Mod, Fun}, Errors, Time}; {'EXIT', Pid, Reason, Time} -> megaco:report_event(20, Mod, ?MODULE, Label ++ " crashed", [TestCase, Config, {'EXIT', Reason}]), @@ -492,14 +492,14 @@ do_eval(ReplyTo, Mod, Fun, Config) -> error:undef -> %% p("do_eval -> error - undef", []), ReplyTo ! {'EXIT', self(), undef, 0}; - exit:{skipped, Reason} -> + exit:{skip, Reason} -> %% p("do_eval -> exit - skipped" %% "~n Reason: ~p", [Reason]), T2 = os:timestamp(), Time = timer:now_diff(T2, T1), display_tc_time(Time), display_system_info("after (skipped)", Mod, Fun), - ReplyTo ! {'EXIT', self(), {skipped, Reason}, Time}; + ReplyTo ! {'EXIT', self(), {skip, Reason}, Time}; exit:{suite_failed, Reason} -> %% p("do_eval -> exit - suite-failed" %% "~n Reason: ~p", [Reason]), @@ -616,9 +616,9 @@ display_result(Res) when is_list(Res) -> Ok = [{MF, Time} || {ok, MF, _, Time} <- Res], Nyi = [MF || {nyi, MF, _, _Time} <- Res], SkippedGrps = [{{M,G}, Reason} || - {skipped, {M, {group, G}}, Reason, _Time} <- Res], + {skip, {M, {group, G}}, Reason, _Time} <- Res], SkippedCases = [{MF, Reason} || - {skipped, {_M, F} = MF, Reason, _Time} <- Res, + {skip, {_M, F} = MF, Reason, _Time} <- Res, is_atom(F)], FailedGrps = [{{M,G}, Reason} || {failed, {M, {group, G}}, Reason, _Time} <- Res], @@ -730,15 +730,18 @@ log(Format, Args, Mod, Line) -> [self(), Mod, Line] ++ Args) end. +skip(Reason) -> + exit({skip, Reason}). + skip(Actual, File, Line) -> - log("Skipping test case~n", [], File, Line), - String = lists:flatten(io_lib:format("Skipping test case ~p(~p): ~p~n", - [File, Line, Actual])), - exit({skipped, String}). + log("Skipping test case: ~p~n", [Actual], File, Line), + String = f("~p(~p): ~p~n", [File, Line, Actual]), + skip(String). fatal_skip(Actual, File, Line) -> error(Actual, File, Line), - exit({skipped, {fatal, Actual, File, Line}}). + skip({fatal, Actual, File, Line}). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -750,7 +753,8 @@ flush() -> after 1000 -> [] end. - + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Check if process is alive and kicking still_alive(Pid) -> @@ -988,11 +992,14 @@ node_to_name_and_host(Node) -> start_nodes([Node | Nodes], File, Line) -> case net_adm:ping(Node) of pong -> + p("node ~p already running", [Node]), start_nodes(Nodes, File, Line); pang -> [Name, Host] = node_to_name_and_host(Node), + p("try start node ~p", [Node]), case slave:start_link(Host, Name) of {ok, NewNode} when NewNode =:= Node -> + p("node ~p started - now set path, cwd and sync", [Node]), Path = code:get_path(), {ok, Cwd} = file:get_cwd(), true = rpc:call(Node, code, set_path, [Path]), @@ -1001,6 +1008,7 @@ start_nodes([Node | Nodes], File, Line) -> {_, []} = rpc:multicall(global, sync, []), start_nodes(Nodes, File, Line); Other -> + p("failed starting node ~p: ~p", [Node, Other]), fatal_skip({cannot_start_node, Node, Other}, File, Line) end end; @@ -1014,4 +1022,4 @@ f(F, A) -> lists:flatten(io_lib:format(F, A)). p(F, A) -> - io:format("~p~w:" ++ F ++ "~n", [self(), ?MODULE |A]). + io:format("~s ~p " ++ F ++ "~n", [?FTS(), self() | A]). diff --git a/lib/megaco/test/megaco_test_lib.hrl b/lib/megaco/test/megaco_test_lib.hrl index 409f8d52e5..546ecaab9a 100644 --- a/lib/megaco/test/megaco_test_lib.hrl +++ b/lib/megaco/test/megaco_test_lib.hrl @@ -32,8 +32,6 @@ -define(ERROR(Reason), megaco_test_lib:error(Reason, ?MODULE, ?LINE)). --define(F(FMT, ARGS), lists:flatten(io_lib:format(FMT, ARGS))). - -define(OS_BASED_SKIP(Skippable), megaco_test_lib:os_based_skip(Skippable)). @@ -83,7 +81,10 @@ -define(SLEEP(MSEC), megaco_test_lib:sleep(MSEC)). -define(HOURS(T), megaco_test_lib:hours(T)). --define(MINUTES(T), megaco_test_lib:minutes(T)). --define(SECONDS(T), megaco_test_lib:seconds(T)). +-define(MINS(T), megaco_test_lib:minutes(T)). +-define(MINUTES(T), ?MINS(T)). +-define(SECS(T), megaco_test_lib:seconds(T)). +-define(SECONDS(T), ?SECS(T)). -define(FTS(), megaco:format_timestamp(erlang:timestamp())). - +-define(FTS(TS), megaco:format_timestamp(TS)). +-define(F(F,A), lists:flatten(io_lib:format(F, A))). diff --git a/lib/megaco/test/megaco_test_megaco_generator.erl b/lib/megaco/test/megaco_test_megaco_generator.erl index 8a37fa33fe..4e0f2e334c 100644 --- a/lib/megaco/test/megaco_test_megaco_generator.erl +++ b/lib/megaco/test/megaco_test_megaco_generator.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -118,7 +118,7 @@ init([]) -> %% ----- instruction parser ----- handle_parse({debug, Debug} = Instruction, State) - when (Debug == true) orelse (Debug == false) -> + when is_boolean(Debug) -> {ok, Instruction, State}; handle_parse({expect_nothing, To} = Instruction, State) @@ -126,9 +126,9 @@ handle_parse({expect_nothing, To} = Instruction, State) {ok, Instruction, State}; handle_parse({megaco_trace, Level} = Instruction, State) - when (Level == disable) orelse - (Level == max) orelse - (Level == min) orelse + when (Level =:= disable) orelse + (Level =:= max) orelse + (Level =:= min) orelse is_integer(Level) -> {ok, Instruction, State}; @@ -1096,11 +1096,10 @@ handle_megaco_callback_reply(_, _, _, _) -> %%---------------------------------------------------------------------- random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random(N) -> - random:uniform(N). + rand:uniform(N). get_config(Key, Opts) -> diff --git a/lib/megaco/test/megaco_test_mg.erl b/lib/megaco/test/megaco_test_mg.erl index d6a9a8c314..38883c94f0 100644 --- a/lib/megaco/test/megaco_test_mg.erl +++ b/lib/megaco/test/megaco_test_mg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -79,7 +79,10 @@ reply_counter = 0, mload_info = undefined, parent = undefined, - dsi_timer}). + dsi_timer, + evs = []}). + +-define(EVS_MAX, 10). %%% -------------------------------------------------------------------- @@ -364,7 +367,7 @@ mg(Parent, Verbosity, Config) -> MG = #mg{parent = Parent, mid = Mid, dsi_timer = DSITimer}, i("mg -> started"), put(verbosity, Verbosity), - case (catch loop(MG)) of + case (catch loop(evs(MG, started))) of {'EXIT', normal} -> exit(normal); {'EXIT', Reason} -> @@ -438,12 +441,12 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> {display_system_info, Time} -> display_system_info(S#mg.mid), NewTimer = create_timer(Time, display_system_info), - loop(S#mg{dsi_timer = NewTimer}); + loop(evs(S#mg{dsi_timer = NewTimer}, {dsi, Time})); {verbosity, V, Parent} -> i("loop -> received new verbosity: ~p", [V]), put(verbosity,V), - loop(S); + loop(evs(S, {verb, V})); {stop, Parent} -> @@ -453,7 +456,7 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> Res = do_stop(Mid), d("loop -> stop result: ~p", [Res]), server_reply(Parent, stopped, {ok, Res}), - exit(normal); + done(evs(S, stop), normal); {{enable_test_code, Tag, Fun}, Parent} -> i("loop -> enable_test_code: ~p, ~p", [Tag, Fun]), @@ -463,52 +466,52 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> "~n ets:tab2list(megaco_test_data): ~p", [Reply,ets:tab2list(megaco_test_data)]), server_reply(Parent, enable_test_code_reply, Reply), - loop(S); + loop(evs(S, {enable_test_code, Tag})); {{encode_ar_first, EAF}, Parent} -> i("loop -> encode_ar_first: ~p", [EAF]), {Reply, S1} = handle_encode_ar_first(S, EAF), server_reply(Parent, encode_ar_first_reply, Reply), - loop(S1#mg{encode_ar_first = EAF}); + loop(evs(S1#mg{encode_ar_first = EAF}, {enc_arf, EAF})); %% Give me statistics {statistics, Parent} -> i("loop -> got request for statistics", []), Stats = do_get_statistics(Mid), server_reply(Parent, statistics_reply, {ok, Stats}), - loop(S); + loop(evs(S, stats)); {reset_stats, Parent} -> i("loop -> got request to reset stats counters", []), do_reset_stats(Mid), server_reply(Parent, reset_stats_ack, ok), - loop(S); + loop(evs(S, rst_stats)); {{user_info, Tag}, Parent} -> i("loop -> got user_info request for ~w", [Tag]), Res = do_get_user_info(Mid, Tag), d("loop -> Res: ~p", [Res]), server_reply(Parent, user_info_ack, Res), - loop(S); + loop(evs(S, {ui, Tag})); {{update_user_info, Tag, Val}, Parent} -> i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]), Res = do_update_user_info(Mid, Tag, Val), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_user_info_ack, Res), - loop(S); + loop(evs(S, {uui, {Tag, Val}})); {{conn_info, Tag}, Parent} -> i("loop -> got conn_info request for ~w", [Tag]), Res = do_get_conn_info(Mid, Tag), server_reply(Parent, conn_info_ack, Res), - loop(S); + loop(evs(S, {ci, Tag})); {{update_conn_info, Tag, Val}, Parent} -> i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]), Res = do_update_conn_info(Mid, Tag, Val), server_reply(Parent, update_conn_info_ack, Res), - loop(S); + loop(evs(S, {uci, {Tag, Val}})); %% Do a service change @@ -526,28 +529,28 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> server_reply(Parent, service_change_reply, Error), S end, - loop(S1); + loop(evs(S1, svc_ch)); {{group_requests, N}, Parent} when N > 0 -> i("loop -> received group_requests ~p", [N]), Reply = {ok, S#mg.group_size}, server_reply(Parent, group_requests_reply, Reply), - loop(S#mg{group_size = N}); + loop(evs(S#mg{group_size = N}, {grp_reqs, N})); {{ack_info, To}, Parent} -> i("loop -> received request to inform about received ack's ", []), - loop(S#mg{ack_info = To}); + loop(evs(S#mg{ack_info = To}, {acki, To})); {{rep_info, To}, Parent} -> i("loop -> received request to inform about received rep's ", []), - loop(S#mg{rep_info = To}); + loop(evs(S#mg{rep_info = To}, {repi, To})); %% Make a sync-call {notify_request, Parent} -> i("loop -> received request to send notify request ", []), {Res, S1} = do_handle_notify_request(S), d("loop -> notify request result: ~p", [Res]), - loop(S1); + loop(evs(S1, not_req)); %% sync-call complete {notify_request_complete, NotifyReply, Pid} -> @@ -556,7 +559,7 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> "~n NotifyReply: ~p", [Pid, NotifyReply]), server_reply(Parent, notify_request_reply, NotifyReply), - loop(S#mg{req_handler = undefined}); + loop(evs(S#mg{req_handler = undefined}, {not_reqc, NotifyReply})); %% cancel requests @@ -564,14 +567,14 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> i("loop -> received request to cancel (all) megaco requests ", []), Res = do_cancel_requests(Mid, Reason), server_reply(Parent, cancel_request_reply, Res), - loop(S); + loop(evs(S, {creq, Reason})); %% Apply multi-load {apply_multi_load, {NL, NR}, Parent} -> i("loop -> received apply_multi_load request: ~w, ~w", [NL, NR]), S1 = start_loaders(S, NL, NR), - loop(S1); + loop(evs(S1, {apply_mload, {NL, NR}})); %% Apply some load @@ -587,12 +590,12 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> server_reply(Parent, apply_load_ack, Error), S end, - loop(S1); + loop(evs(S1, {apply_load, Times})); {apply_load_timeout, _} -> d("loop -> received apply_load timeout", []), S1 = do_apply_load(S), - loop(S1); + loop(evs(S1, apply_loadto)); %% Megaco callback messages @@ -604,22 +607,35 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> {Reply, S1} = handle_megaco_request(S, Request), d("loop -> send (megaco callback) request reply: ~n~p", [Reply]), From ! {reply, Reply, self()}, - loop(S1); + loop(evs(S1, {req, {Request, Mid, From}})); {'EXIT', Pid, Reason} -> - i("loop -> received exit signal from ~p: ~n~p", [Pid, Reason]), + i("loop -> received exit signal from ~p: " + "~n ~p", [Pid, Reason]), S1 = handle_exit(S, Pid, Reason), - loop(S1); + loop(evs(S1, {exit, {Pid, Reason}})); Invalid -> error_msg("received invalid request: ~n~p", [Invalid]), - loop(S) + loop(evs(S, {invalid, Invalid})) end. +evs(#mg{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) -> + S#mg{evs = [{?FTS(), Ev}|EVS]}; +evs(#mg{evs = EVS} = S, Ev) -> + S#mg{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}. + +done(#mg{evs = EVS}, Reason) -> + info_msg("Exiting with latest event(s): " + "~n ~p" + "~n", [EVS]), + exit(Reason). + + handle_encode_ar_first(#mg{encode_ar_first = Old} = MG, New) when (New =:= true) orelse (New =:= false) -> {{ok, Old}, MG#mg{encode_ar_first = New}}; @@ -776,7 +792,7 @@ do_service_change(#mg{state = State} = MG) -> {{error, {invalid_state, State}}, MG}. do_service_change(ConnHandle, Method, EAF, Reason) -> - d("sending service change using:" + d("send service change using:" "~n ConnHandle: ~p" "~n Method: ~p" "~n EAF: ~p" @@ -844,10 +860,10 @@ loader_main(EAF, N, CH) -> -handle_exit(#mg{parent = Pid}, Pid, Reason) -> +handle_exit(#mg{parent = Pid} = S, Pid, Reason) -> error_msg("received exit from the parent:" "~n ~p", [Reason]), - exit({parent_terminated, Reason}); + done(S, {parent_terminated, Reason}); handle_exit(#mg{parent = Parent, req_handler = Pid} = MG, Pid, Reason) -> error_msg("received unexpected exit from the request handler:" @@ -1423,6 +1439,7 @@ sleep(X) -> receive after X -> ok end. +info_msg(F,A) -> error_logger:info_msg("MG: " ++ F ++ "~n",A). error_msg(F,A) -> error_logger:error_msg("MG: " ++ F ++ "~n",A). @@ -1536,37 +1553,28 @@ print(Severity, Verbosity, P, F, A) -> print(true, P, F, A) -> io:format("*** [~s] ~s ~p ~s ***" "~n " ++ F ++ "~n~n", - [format_timestamp(now()), P, self(), get(sname) | A]); + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random() -> random(50). random(N) -> - random:uniform(N). + rand:uniform(N). display_system_info(Mid) -> display_system_info(Mid, ""). display_system_info(Mid, Pre) -> - TimeStr = format_timestamp(now()), + TimeStr = ?FTS(), MibStr = lists:flatten(io_lib:format("~p ", [Mid])), megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr). diff --git a/lib/megaco/test/megaco_test_mgc.erl b/lib/megaco/test/megaco_test_mgc.erl index 045bc7c9fd..a9027ca68e 100644 --- a/lib/megaco/test/megaco_test_mgc.erl +++ b/lib/megaco/test/megaco_test_mgc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2019. 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. @@ -75,7 +75,10 @@ abort_info = undefined, req_info = undefined, mg = [], - dsi_timer}). + dsi_timer, + evs = []}). + +-define(EVS_MAX, 10). %%% ------------------------------------------------------------------ @@ -297,7 +300,7 @@ mgc(Parent, Verbosity, Config) -> dsi_timer = DSITimer}, i("mgc -> started"), display_system_info("at start "), - loop(S) + loop(evs(S, started)) end. init(Config) -> @@ -359,7 +362,7 @@ loop(S) -> {display_system_info, Time} -> display_system_info(S#mgc.mid), NewTimer = create_timer(Time, display_system_info), - loop(S#mgc{dsi_timer = NewTimer}); + loop(evs(S#mgc{dsi_timer = NewTimer}, {dsi, Time})); {stop, Parent} when S#mgc.parent =:= Parent -> i("loop -> stopping", []), @@ -371,7 +374,7 @@ loop(S) -> application:stop(megaco), i("loop -> stopped", []), server_reply(Parent, stopped, ok), - exit(normal); + done(evs(S, stop), normal); {{disconnect, Reason}, Parent} when S#mgc.parent == Parent -> i("loop -> disconnecting", []), @@ -379,22 +382,22 @@ loop(S) -> [Conn|_] = megaco:user_info(Mid, connections), Res = megaco:disconnect(Conn, {self(), Reason}), server_reply(Parent, disconnected, Res), - loop(S); + loop(evs(S, {disconnect, Reason})); {{update_user_info, Tag, Val}, Parent} when S#mgc.parent == Parent -> i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]), Res = (catch megaco:update_user_info(S#mgc.mid, Tag, Val)), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_user_info_ack, Res), - loop(S); - - {{user_info, Tag}, Parent} when S#mgc.parent == Parent -> - i("loop -> got user_info request for ~w", [Tag]), - Res = (catch megaco:user_info(S#mgc.mid, Tag)), - d("loop -> Res: ~p", [Res]), - server_reply(Parent, user_info_ack, Res), - loop(S); - + loop(evs(S, {uui, {Tag, Val}})); + + {{user_info, Tag}, Parent} when S#mgc.parent == Parent -> + i("loop -> got user_info request for ~w", [Tag]), + Res = (catch megaco:user_info(S#mgc.mid, Tag)), + d("loop -> Res: ~p", [Res]), + server_reply(Parent, user_info_ack, Res), + loop(evs(S, {ui, Tag})); + {{update_conn_info, Tag, Val}, Parent} when S#mgc.parent == Parent -> i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]), Conns = megaco:user_info(S#mgc.mid, connections), @@ -404,7 +407,7 @@ loop(S) -> Res = lists:map(Fun, Conns), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_conn_info_ack, Res), - loop(S); + loop(evs(S, {uci, {Tag, Val}})); {{conn_info, Tag}, Parent} when S#mgc.parent == Parent -> i("loop -> got conn_info request for ~w", [Tag]), @@ -415,11 +418,11 @@ loop(S) -> Res = lists:map(Fun, Conns), d("loop -> Res: ~p", [Res]), server_reply(Parent, conn_info_ack, Res), - loop(S); + loop(evs(S, {ci, Tag})); %% - {request_action, {Action, To}, Parent} when S#mgc.parent == Parent -> + {request_action, {Action, To}, Parent} when S#mgc.parent == Parent -> i("loop -> got new request_action: ~p:~w", [Action,To]), {Reply, S1} = case lists:member(Action, ?valid_actions) of @@ -432,7 +435,7 @@ loop(S) -> {{error, {invalid_action, Action}}, S} end, server_reply(Parent, request_action_ack, Reply), - loop(S1); + loop(evs(S1, {req_act, {Action, To}})); %% Reset stats @@ -440,7 +443,7 @@ loop(S) -> i("loop -> got request to reset stats counters"), do_reset_stats(S#mgc.mid), server_reply(Parent, reset_stats_ack, ok), - loop(S); + loop(evs(S, rst_stats)); %% Give me statistics @@ -466,7 +469,7 @@ loop(S) -> lists:map(GetTrans, megaco:user_info(Mid, connections)), Reply = {ok, [{gen, Gen}, {trans, Trans}]}, server_reply(Parent, {statistics_reply, 1}, Reply), - loop(S); + loop(evs(S, {stats, 1})); {{statistics, 2}, Parent} when S#mgc.parent == Parent -> @@ -477,7 +480,7 @@ loop(S) -> UdpStats = get_trans_stats(UdpSup, megaco_udp), Reply = {ok, [{gen, Gen}, {trans, [TcpStats, UdpStats]}]}, server_reply(Parent, {statistics_reply, 2}, Reply), - loop(S); + loop(evs(S, {stats, 2})); %% Megaco callback messages @@ -487,28 +490,28 @@ loop(S) -> {Reply, S1} = handle_megaco_request(Request, S), d("loop -> send request reply: ~n~p", [Reply]), reply(From, Reply), - loop(S1); + loop(evs(S1, {req, Request})); {ack_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received ack's ", []), - loop(S#mgc{ack_info = To}); + loop(evs(S#mgc{ack_info = To}, {acki, To})); {abort_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received aborts ", []), - loop(S#mgc{abort_info = To}); + loop(evs(S#mgc{abort_info = To}, {abi, To})); {req_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received req's ", []), - loop(S#mgc{req_info = To}); + loop(evs(S#mgc{req_info = To}, {reqi, To})); {verbosity, V, Parent} when S#mgc.parent == Parent -> i("loop -> received new verbosity: ~p", [V]), put(verbosity,V), - loop(S); + loop(evs(S, {verb, V})); {'EXIT', Pid, Reason} when S#mgc.tcp_sup =:= Pid -> @@ -525,7 +528,7 @@ loop(S) -> i("loop -> stopped", []), StopReason = {error, {tcp_terminated, Pid, Reason}}, server_reply(S#mgc.parent, stopped, StopReason), - exit(StopReason); + done(evs(S, {tcp_sup_exit, Reason}), StopReason); {'EXIT', Pid, Reason} when S#mgc.udp_sup =:= Pid -> @@ -542,15 +545,27 @@ loop(S) -> i("loop -> stopped", []), StopReason = {error, {udp_terminated, Pid, Reason}}, server_reply(S#mgc.parent, stopped, StopReason), - exit(StopReason); + done(evs(S, {udp_sup_exit, Reason}), StopReason); Invalid -> i("loop -> received invalid request: ~p", [Invalid]), - loop(S) + loop(evs(S, {invalid, Invalid})) end. +evs(#mgc{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) -> + S#mgc{evs = [{?FTS(), Ev}|EVS]}; +evs(#mgc{evs = EVS} = S, Ev) -> + S#mgc{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}. + +done(#mgc{evs = EVS}, Reason) -> + info_msg("Exiting with latest event(s): " + "~n ~p" + "~n", [EVS]), + exit(Reason). + + do_reset_stats(Mid) -> megaco:reset_stats(), do_reset_trans_stats(megaco:user_info(Mid, connections), []). @@ -825,10 +840,10 @@ handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, S) -> d("handle_megaco_request(handle_trans_ack) -> entry with" - "~n CH: ~p" - "~n PV: ~p" - "~n AS: ~p" - "~n AD: ~p", [CH, PV, AS, AD]), + "~n Conn Handle: ~p" + "~n Prot Version: ~p" + "~n Ack Status: ~p" + "~n Ack Data: ~p", [CH, PV, AS, AD]), {ok, S}; handle_megaco_request({handle_unexpected_trans, CH, PV, TR}, S) -> @@ -1090,6 +1105,7 @@ sleep(X) -> receive after X -> ok end. +info_msg(F,A) -> error_logger:info_msg("MGC: " ++ F ++ "~n",A). error_msg(F,A) -> error_logger:error_msg("MGC: " ++ F ++ "~n",A). @@ -1153,18 +1169,17 @@ get_conf(Key, Config, Default) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random(N) -> - random:uniform(N). + rand:uniform(N). display_system_info(Mid) -> display_system_info(Mid, ""). display_system_info(Mid, Pre) -> - TimeStr = format_timestamp(now()), + TimeStr = ?FTS(), MibStr = lists:flatten(io_lib:format("~p ", [Mid])), megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr). @@ -1209,14 +1224,6 @@ print(_, _, _, _) -> print(P, F, A) -> io:format("*** [~s] ~s ~p ~s ***" "~n " ++ F ++ "~n~n", - [format_timestamp(now()), P, self(), get(sname) | A]). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). + [?FTS(), P, self(), get(sname) | A]). + diff --git a/lib/megaco/test/megaco_timer_test.erl b/lib/megaco/test/megaco_timer_test.erl index d3e8e27636..84c314d8ed 100644 --- a/lib/megaco/test/megaco_timer_test.erl +++ b/lib/megaco/test/megaco_timer_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -362,6 +362,8 @@ integer_timer_start_and_stop(Config) when is_list(Config) -> case tmr_stop(Ref) of ok -> ok; + {ok, _} -> + ok; CancelRes -> ?SKIP({cancel_failed, CancelRes}) end diff --git a/lib/megaco/test/megaco_trans_test.erl b/lib/megaco/test/megaco_trans_test.erl index fb44a3c6e6..37bc134c8d 100644 --- a/lib/megaco/test/megaco_trans_test.erl +++ b/lib/megaco/test/megaco_trans_test.erl @@ -295,7 +295,7 @@ multi_ack_timeout(doc) -> []; multi_ack_timeout(Config) when is_list(Config) -> %% <CONDITIONAL-SKIP> - Skippable = [win32, {unix, [darwin, linux]}], + Skippable = [win32, {unix, [darwin, linux, sunos]}], % Is there any left? Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> @@ -9247,10 +9247,10 @@ await_ack(User, N, Timeout, Expected) when (N > 0) andalso is_integer(Timeout) - T = tim(), receive {ack_received, User, Expected} -> - d("await_ack -> received another ack"), + d("await_ack -> received another expected ack"), await_ack(User, N-1, Timeout - (tim() - T), Expected); {ack_received, User, UnExpected} -> - e("await_ack -> unexpected ack result: ~p", [UnExpected]), + e("await_ack -> received unexpected ack result: ~p", [UnExpected]), exit({unexpected_ack_result, UnExpected, Expected}) after Timeout -> exit({await_ack_timeout, N}) @@ -9266,35 +9266,6 @@ await_ack(User, N, infinity, Expected) when N > 0 -> exit({unexpected_ack_result, UnExpected, Expected}) end. -%% await_req(_User, 0, Timeout) -> -%% d("await_req -> done when Timeout = ~p", [Timeout]), -%% ok; -%% await_req(User, N, Timeout) when (N > 0) andalso is_integer(Timeout) -> -%% d("await_req -> entry with N: ~p, Timeout: ~p", [N,Timeout]), -%% T = tim(), -%% receive -%% {req_received, User, ARs} -> -%% d("await_req -> received req(s) when N = ~w", [N]), -%% N1 = await_req1(N, ARs), -%% await_req(User, N1, Timeout - (tim() - T)) -%% after Timeout -> -%% exit({await_req_timeout, N}) -%% end; -%% await_req(User, N, infinity) when N > 0 -> -%% d("await_req -> entry with N: ~p", [N]), -%% receive -%% {req_received, User, ARs} -> -%% d("await_req -> received req(s) when N = ~2",[N]), -%% N1 = await_req1(N, ARs), -%% await_req(User, N1, infinity) -%% end. - -%% await_req1(N, []) when N >= 0 -> -%% N; -%% await_req1(N, [AR|ARs]) when (N > 0) andalso is_record(AR, 'ActionRequest') -> -%% await_req1(N-1, ARs); -%% await_req1(N, ARs) -> -%% exit({unexpected_req_result, N, ARs}). tim() -> {A,B,C} = erlang:timestamp(), diff --git a/lib/megaco/test/megaco_udp_test.erl b/lib/megaco/test/megaco_udp_test.erl index cc03ec733a..39ff44709e 100644 --- a/lib/megaco/test/megaco_udp_test.erl +++ b/lib/megaco/test/megaco_udp_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2019. 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. @@ -1211,23 +1211,14 @@ p(F, A) -> p(S, F, A) when is_list(S) -> io:format("*** [~s] ~p ~s ***" "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), S | A]); + [?FTS(), self(), S | A]); p(_S, F, A) -> io:format("*** [~s] ~p ~s *** " "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), "undefined" | A]). + [?FTS(), self(), "undefined" | A]). ms() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). + erlang:monotonic_time(milli_seconds). -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc index ffbbdadec0..968e89a745 100644 --- a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc +++ b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc @@ -295,7 +295,7 @@ ok</pre> if the log is large. Notice that the <c>Mnesia</c> system continues to operate during log dumps.</p> <p>By default <c>Mnesia</c> either dumps the log whenever - 100 records have + 1000 records have been written in the log or when three minutes have passed. This is controlled by the two application parameters <c>-mnesia dump_log_write_threshold WriteOperations</c> and diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index 882de0d613..0f221b0c1f 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -331,35 +331,39 @@ release_schema_commit_lock() -> %% Special for preparation of add table copy get_network_copy(Tid, Tab, Cs) -> -% We can't let the controller queue this one -% because that may cause a deadlock between schema_operations -% and initial tableloadings which both takes schema locks. -% But we have to get copier_done msgs when the other side -% goes down. - call({add_other, self()}), - Reason = {dumper,{add_table_copy, Tid}}, - Work = #net_load{table = Tab,reason = Reason,cstruct = Cs}, - %% I'll need this cause it's linked trough the subscriber - %% might be solved by using monitor in subscr instead. - process_flag(trap_exit, true), - Load = load_table_fun(Work), - Res = ?CATCH(Load()), - process_flag(trap_exit, false), - call({del_other, self()}), - case Res of - #loader_done{is_loaded = true} -> - Tab = Res#loader_done.table_name, - case Res#loader_done.needs_announce of - true -> - i_have_tab(Tab); - false -> - ignore - end, - Res#loader_done.reply; - #loader_done{} -> - Res#loader_done.reply; - Else -> - {not_loaded, Else} + %% We can't let the controller queue this one + %% because that may cause a deadlock between schema_operations + %% and initial tableloadings which both takes schema locks. + %% But we have to get copier_done msgs when the other side + %% goes down. + case call({add_other, self()}) of + ok -> + Reason = {dumper,{add_table_copy, Tid}}, + Work = #net_load{table = Tab,reason = Reason,cstruct = Cs}, + %% I'll need this cause it's linked trough the subscriber + %% might be solved by using monitor in subscr instead. + process_flag(trap_exit, true), + Load = load_table_fun(Work), + Res = ?CATCH(Load()), + process_flag(trap_exit, false), + call({del_other, self()}), + case Res of + #loader_done{is_loaded = true} -> + Tab = Res#loader_done.table_name, + case Res#loader_done.needs_announce of + true -> + i_have_tab(Tab); + false -> + ignore + end, + Res#loader_done.reply; + #loader_done{} -> + Res#loader_done.reply; + Else -> + {not_loaded, Else} + end; + {error, Else} -> + {not_loaded, Else} end. %% This functions is invoked from the dumper @@ -772,6 +776,18 @@ handle_call({unannounce_add_table_copy, [Tab, Node], From}, ReplyTo, State) -> noreply(State#state{early_msgs = [{call, Msg, undefined} | Msgs]}) end; +handle_call({add_other, Who}, _From, State = #state{others=Others0, schema_is_merged=SM}) -> + case SM of + true -> + Others = [Who|Others0], + {reply, ok, State#state{others=Others}}; + false -> + {reply, {error, {not_active,schema,node()}}, State} + end; +handle_call({del_other, Who}, _From, State = #state{others=Others0}) -> + Others = lists:delete(Who, Others0), + {reply, ok, State#state{others=Others}}; + handle_call(Msg, From, State) when State#state.schema_is_merged /= true -> %% Buffer early messages Msgs = State#state.early_msgs, @@ -803,13 +819,6 @@ handle_call({block_table, [Tab], From}, _Dummy, State) -> handle_call({check_w2r, _Node, Tab}, _From, State) -> {reply, val({Tab, where_to_read}), State}; -handle_call({add_other, Who}, _From, State = #state{others=Others0}) -> - Others = [Who|Others0], - {reply, ok, State#state{others=Others}}; -handle_call({del_other, Who}, _From, State = #state{others=Others0}) -> - Others = lists:delete(Who, Others0), - {reply, ok, State#state{others=Others}}; - handle_call(Msg, _From, State) -> error("~p got unexpected call: ~tp~n", [?SERVER_NAME, Msg]), noreply(State). diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl index f68626413e..0222c5b1a0 100644 --- a/lib/mnesia/src/mnesia_locker.erl +++ b/lib/mnesia/src/mnesia_locker.erl @@ -774,10 +774,12 @@ do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) -> N = node(), receive {?MODULE, N, granted} -> + ?ets_insert(Store, {sticky, true}), ?ets_insert(Store, {{locks, Tab, Key}, write}), [?ets_insert(Store, {nodes, Node}) || Node <- WNodes], granted; {?MODULE, N, {granted, Val}} -> %% for rwlocks + ?ets_insert(Store, {sticky, true}), case opt_lookup_in_client(Val, Oid, write) of C = #cyclic{} -> exit({aborted, C}); diff --git a/lib/mnesia/src/mnesia_monitor.erl b/lib/mnesia/src/mnesia_monitor.erl index 4cfe16dec0..4e50b46da8 100644 --- a/lib/mnesia/src/mnesia_monitor.erl +++ b/lib/mnesia/src/mnesia_monitor.erl @@ -83,9 +83,9 @@ going_down = [], tm_started = false, early_connects = [], connecting, mq = [], remote_node_status = []}). --define(current_protocol_version, {8,3}). +-define(current_protocol_version, {8,4}). --define(previous_protocol_version, {8,2}). +-define(previous_protocol_version, {8,3}). start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, @@ -196,7 +196,7 @@ protocol_version() -> %% A sorted list of acceptable protocols the %% preferred protocols are first in the list acceptable_protocol_versions() -> - [protocol_version(), ?previous_protocol_version, {8,1}]. + [protocol_version(), ?previous_protocol_version]. needs_protocol_conversion(Node) -> case {?catch_val({protocol, Node}), protocol_version()} of diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl index ef38adca1e..d0f5d0e07b 100644 --- a/lib/mnesia/src/mnesia_schema.erl +++ b/lib/mnesia/src/mnesia_schema.erl @@ -697,7 +697,7 @@ schema_coordinator(Client, _Fun, undefined) -> schema_coordinator(Client, Fun, Controller) when is_pid(Controller) -> %% Do not trap exit in order to automatically die %% when the controller dies - + put(transaction_client, Client), %% debug link(Controller), unlink(Client), @@ -730,7 +730,10 @@ api_list2cs(Other) -> mnesia:abort({badarg, Other}). vsn_cs2list(Cs) -> - cs2list(need_old_cstructs(), Cs). + cs2list(Cs). + +cs2list(false, Cs) -> + cs2list(Cs). cs2list(Cs) when is_record(Cs, cstruct) -> Tags = record_info(fields, cstruct), @@ -755,25 +758,6 @@ cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 19 -> cookie,version], rec2list(Tags, Tags, 2, Cs). -cs2list(false, Cs) -> - cs2list(Cs); -cs2list({8,3}, Cs) -> - cs2list(Cs); -cs2list({8,Minor}, Cs) when Minor =:= 2; Minor =:= 1 -> - Orig = record_info(fields, cstruct), - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, - load_order,access_mode,majority,index,snmp,local_content, - record_name,attributes, - user_properties,frag_properties,storage_properties, - cookie,version], - CsList = rec2list(Tags, Orig, 2, Cs), - case proplists:get_value(index, CsList, []) of - [] -> CsList; - NewFormat -> - OldFormat = [Pos || {Pos, _Pref} <- NewFormat], - lists:keyreplace(index, 1, CsList, {index, OldFormat}) - end. - rec2list([index | Tags], [index|Orig], Pos, Rec) -> Val = element(Pos, Rec), [{index, lists:map( @@ -796,19 +780,8 @@ rec2list([], _, _Pos, _Rec) -> rec2list(Tags, [_|Orig], Pos, Rec) -> rec2list(Tags, Orig, Pos+1, Rec). -normalize_cs(Cstructs, Node) -> - %% backward-compatibility hack; normalize before returning - case need_old_cstructs([Node]) of - false -> - Cstructs; - Version -> - %% some other format - [convert_cs(Version, Cs) || Cs <- Cstructs] - end. - -convert_cs(Version, Cs) -> - Fields = [Value || {_, Value} <- cs2list(Version, Cs)], - list_to_tuple([cstruct|Fields]). +normalize_cs(Cstructs, _Node) -> + Cstructs. list2cs(List) -> list2cs(List, get_ext_types()). @@ -1864,11 +1837,7 @@ do_move_table(schema, _FromNode, _ToNode) -> mnesia:abort({bad_type, schema}); do_move_table(Tab, FromNode, ToNode) when is_atom(FromNode), is_atom(ToNode) -> TidTs = get_tid_ts_and_lock(schema, write), - AnyOld = lists:any(fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, - [ToNode|val({Tab, where_to_write})]), - if AnyOld -> ignore; %% Leads to deadlock on old nodes - true -> get_tid_ts_and_lock(Tab, write) - end, + get_tid_ts_and_lock(Tab, write), insert_schema_ops(TidTs, make_move_table(Tab, FromNode, ToNode)); do_move_table(Tab, FromNode, ToNode) -> mnesia:abort({badarg, Tab, FromNode, ToNode}). @@ -3438,15 +3407,14 @@ do_merge_schema(LockTabs0) -> mnesia_lib:intersect(Ns,NeedsLock)) || {T,Ns} <- LockTabs], - NeedsConversion = need_old_cstructs(NeedsLock ++ LockedAlready), {value, SchemaCs} = lists:keysearch(schema, #cstruct.name, Cstructs), - SchemaDef = cs2list(NeedsConversion, SchemaCs), + SchemaDef = cs2list(false, SchemaCs), %% Announce that Node is running A = [{op, announce_im_running, node(), SchemaDef, Running, RemoteRunning}], do_insert_schema_ops(Store, A), %% Introduce remote tables to local node - do_insert_schema_ops(Store, make_merge_schema(Node, NeedsConversion, Cstructs)), + do_insert_schema_ops(Store, make_merge_schema(Node, false, Cstructs)), %% Introduce local tables to remote nodes Tabs = val({schema, tables}), @@ -3471,23 +3439,7 @@ do_merge_schema(LockTabs0) -> end. fetch_cstructs(Node) -> - Convert = mnesia_monitor:needs_protocol_conversion(Node), - case rpc:call(Node, mnesia_controller, get_remote_cstructs, []) of - {cstructs, Cs0, RemoteRunning1} when Convert -> - {cstructs, [list2cs(cs2list(Cs)) || Cs <- Cs0], RemoteRunning1}; - Result -> - Result - end. - -need_old_cstructs() -> - need_old_cstructs(val({schema, where_to_write})). - -need_old_cstructs(Nodes) -> - Filter = fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, - case lists:filter(Filter, Nodes) of - [] -> false; - Ns -> lists:min([element(1, ?catch_val({protocol, Node})) || Node <- Ns]) - end. + rpc:call(Node, mnesia_controller, get_remote_cstructs, []). tab_to_nodes(Tab) when is_atom(Tab) -> Cs = val({Tab, cstruct}), diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl index 8b79fca1d7..8a4113422a 100644 --- a/lib/mnesia/src/mnesia_tm.erl +++ b/lib/mnesia/src/mnesia_tm.erl @@ -26,7 +26,7 @@ init/1, non_transaction/5, transaction/6, - commit_participant/5, + commit_participant/6, dirty/2, display_info/2, do_update_op/3, @@ -62,13 +62,14 @@ %% Format on coordinators is [{Tid, EtsTabList} ..... -record(prep, {protocol = sym_trans, - %% async_dirty | sync_dirty | sym_trans | sync_sym_trans | asym_trans + %% async_dirty | sync_dirty | sym_trans | sync_sym_trans | asym_trans | sync_asym_trans records = [], prev_tab = [], % initiate to a non valid table name prev_types, prev_snmp, types, - majority = [] + majority = [], + sync = false }). -record(participant, {tid, pid, commit, disc_nodes = [], @@ -250,11 +251,13 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), Commit = new_cr_format(Commit0), Pid = - case Protocol of - asym_trans when node(Tid#tid.pid) /= node() -> - Args = [tmpid(From), Tid, Commit, DiscNs, RamNs], + if + node(Tid#tid.pid) =:= node() -> + error({internal_error, local_node}); + Protocol =:= asym_trans orelse Protocol =:= sync_asym_trans -> + Args = [Protocol, tmpid(From), Tid, Commit, DiscNs, RamNs], spawn_link(?MODULE, commit_participant, Args); - _ when node(Tid#tid.pid) /= node() -> %% *_sym_trans + true -> %% *_sym_trans reply(From, {vote_yes, Tid}), nopid end, @@ -1190,7 +1193,15 @@ do_arrange(Tid, Store, RestoreKey, Prep, N) when RestoreKey == restore_op -> P2 = Prep#prep{protocol = asym_trans, records = Recs2}, do_arrange(Tid, Store, ?ets_next(Store, RestoreKey), P2, N + 1); do_arrange(_Tid, _Store, '$end_of_table', Prep, N) -> - {N, Prep}; + case Prep of + #prep{sync=true, protocol=asym_trans} -> + {N, Prep#prep{protocol=sync_asym_trans}}; + _ -> + {N, Prep} + end; +do_arrange(Tid, Store, sticky, Prep, N) -> + P2 = Prep#prep{sync=true}, + do_arrange(Tid, Store, ?ets_next(Store, sticky), P2, N); do_arrange(Tid, Store, IgnoredKey, Prep, N) -> %% locks, nodes ... local atoms... do_arrange(Tid, Store, ?ets_next(Store, IgnoredKey), Prep, N). @@ -1448,7 +1459,8 @@ multi_commit(sync_sym_trans, _Maj = [], Tid, CR, Store) -> [{tid, Tid}, {outcome, Outcome}]), Outcome; -multi_commit(asym_trans, Majority, Tid, CR, Store) -> +multi_commit(Protocol, Majority, Tid, CR, Store) + when Protocol =:= asym_trans; Protocol =:= sync_asym_trans -> %% This more expensive commit protocol is used when %% table definitions are changed (schema transactions). %% It is also used when the involved tables are @@ -1515,7 +1527,7 @@ multi_commit(asym_trans, Majority, Tid, CR, Store) -> end, Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), ?ets_insert(Store, Pending), - {WaitFor, Local} = ask_commit(asym_trans, Tid, CR2, DiscNs, RamNs), + {WaitFor, Local} = ask_commit(Protocol, Tid, CR2, DiscNs, RamNs), SchemaPrep = ?CATCH(mnesia_schema:prepare_commit(Tid, Local, {coord, WaitFor})), {Votes, Pids} = rec_all(WaitFor, Tid, do_commit, []), @@ -1563,38 +1575,38 @@ multi_commit(asym_trans, Majority, Tid, CR, Store) -> %% Returns do_commit or {do_abort, Reason} rec_acc_pre_commit([Pid | Tail], Tid, Store, Commit, Res, DumperMode, - GoodPids, SchemaAckPids) -> + GoodPids, AckPids) -> receive {?MODULE, _, {acc_pre_commit, Tid, Pid, true}} -> rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); + [Pid | GoodPids], [Pid | AckPids]); {?MODULE, _, {acc_pre_commit, Tid, Pid, false}} -> rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], SchemaAckPids); + [Pid | GoodPids], AckPids); {?MODULE, _, {acc_pre_commit, Tid, Pid}} -> %% Kept for backwards compatibility. Remove after Mnesia 4.x rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); + [Pid | GoodPids], [Pid | AckPids]); {?MODULE, _, {do_abort, Tid, Pid, _Reason}} -> AbortRes = {do_abort, {bad_commit, node(Pid)}}, rec_acc_pre_commit(Tail, Tid, Store, Commit, AbortRes, DumperMode, - GoodPids, SchemaAckPids); + GoodPids, AckPids); {mnesia_down, Node} when Node == node(Pid) -> AbortRes = {do_abort, {bad_commit, Node}}, ?SAFE(Pid ! {Tid, AbortRes}), %% Tell him that he has died rec_acc_pre_commit(Tail, Tid, Store, Commit, AbortRes, DumperMode, - GoodPids, SchemaAckPids) + GoodPids, AckPids) end; -rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, SchemaAckPids) -> +rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, AckPids) -> D = Commit#commit.decision, case Res of do_commit -> %% Now everybody knows that the others %% has voted yes. We also know that %% everybody are uncertain. - prepare_sync_schema_commit(Store, SchemaAckPids), + prepare_sync_schema_commit(Store, AckPids), tell_participants(GoodPids, {Tid, committed}), D2 = D#decision{outcome = committed}, mnesia_recover:log_decision(D2), @@ -1606,7 +1618,7 @@ rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, Sc do_commit(Tid, Commit, DumperMode), ?eval_debug_fun({?MODULE, rec_acc_pre_commit_done_commit}, [{tid, Tid}]), - sync_schema_commit(Tid, Store, SchemaAckPids), + sync_schema_commit(Tid, Store, AckPids), mnesia_locker:release_tid(Tid), ?MODULE ! {delete_transaction, Tid}; @@ -1623,6 +1635,7 @@ rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, Sc Res. %% Note all nodes in case of mnesia_down mgt +%% sync_schema_commit is (ab)used for sync_asym_trans as well. prepare_sync_schema_commit(_Store, []) -> ok; prepare_sync_schema_commit(Store, [Pid | Pids]) -> @@ -1648,17 +1661,17 @@ tell_participants([Pid | Pids], Msg) -> tell_participants([], _Msg) -> ok. --spec commit_participant(_, _, _, _, _) -> no_return(). +-spec commit_participant(_, _, _, _, _, _) -> no_return(). %% Trap exit because we can get a shutdown from application manager -commit_participant(Coord, Tid, Bin, DiscNs, RamNs) when is_binary(Bin) -> +commit_participant(Protocol, Coord, Tid, Bin, DiscNs, RamNs) when is_binary(Bin) -> process_flag(trap_exit, true), Commit = binary_to_term(Bin), - commit_participant(Coord, Tid, Bin, Commit, DiscNs, RamNs); -commit_participant(Coord, Tid, C = #commit{}, DiscNs, RamNs) -> + commit_participant(Protocol, Coord, Tid, Bin, Commit, DiscNs, RamNs); +commit_participant(Protocol, Coord, Tid, C = #commit{}, DiscNs, RamNs) -> process_flag(trap_exit, true), - commit_participant(Coord, Tid, C, C, DiscNs, RamNs). + commit_participant(Protocol, Coord, Tid, C, C, DiscNs, RamNs). -commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> +commit_participant(Protocol, Coord, Tid, Bin, C0, DiscNs, _RamNs) -> ?eval_debug_fun({?MODULE, commit_participant, pre}, [{tid, Tid}]), try mnesia_schema:prepare_commit(Tid, C0, {part, Coord}) of {Modified, C = #commit{}, DumperMode} -> @@ -1683,8 +1696,9 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> mnesia_recover:log_decision(D#decision{outcome = unclear}), ?eval_debug_fun({?MODULE, commit_participant, pre_commit}, [{tid, Tid}]), - Expect_schema_ack = C#commit.schema_ops /= [], - reply(Coord, {acc_pre_commit, Tid, self(), Expect_schema_ack}), + ExpectAck = C#commit.schema_ops /= [] + orelse Protocol =:= sync_asym_trans, + reply(Coord, {acc_pre_commit, Tid, self(), ExpectAck}), %% Now we are vulnerable for failures, since %% we cannot decide without asking others @@ -1694,7 +1708,7 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> ?eval_debug_fun({?MODULE, commit_participant, log_commit}, [{tid, Tid}]), do_commit(Tid, C, DumperMode), - case Expect_schema_ack of + case ExpectAck of false -> ignore; true -> reply(Coord, {schema_commit, Tid, self()}) end, @@ -1978,7 +1992,7 @@ sync_send_dirty(Tid, [Head | Tail], Tab, WaitFor) -> Res = do_dirty(Tid, Head), {WF, Res}; true -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, sync_send_dirty(Tid, Tail, Tab, [Node | WaitFor]) end; sync_send_dirty(_Tid, [], _Tab, WaitFor) -> @@ -1997,11 +2011,11 @@ async_send_dirty(Tid, [Head | Tail], Tab, ReadNode, WaitFor, Res) -> NewRes = do_dirty(Tid, Head), async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, NewRes); ReadNode == Node -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, NewRes = {'EXIT', {aborted, {node_not_running, Node}}}, async_send_dirty(Tid, Tail, Tab, ReadNode, [Node | WaitFor], NewRes); true -> - {?MODULE, Node} ! {self(), {async_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {async_dirty, Tid, Head, Tab}}, async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, Res) end; async_send_dirty(_Tid, [], _Tab, _ReadNode, WaitFor, Res) -> @@ -2058,24 +2072,20 @@ ask_commit(Protocol, Tid, [Head | Tail], DiscNs, RamNs, WaitFor, Local) -> Node == node() -> ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, WaitFor, Head); true -> - CR = ext_format(Head), - Msg = {ask_commit, Protocol, Tid, CR, DiscNs, RamNs}, + Msg = {ask_commit, convert_old(Protocol, Node), Tid, Head, DiscNs, RamNs}, {?MODULE, Node} ! {self(), Msg}, ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, [Node | WaitFor], Local) end; ask_commit(_Protocol, _Tid, [], _DiscNs, _RamNs, WaitFor, Local) -> {WaitFor, Local}. -ext_format(#commit{ext=[]}=CR) -> CR; -ext_format(#commit{node=Node, ext=Ext}=CR) -> +convert_old(sync_asym_trans, Node) -> case mnesia_monitor:needs_protocol_conversion(Node) of - true -> - case lists:keyfind(snmp, 1, Ext) of - false -> CR#commit{ext=[]}; - {snmp, List} -> CR#commit{ext=List} - end; - false -> CR - end. + true -> asym_trans; + false -> sync_asym_trans + end; +convert_old(Protocol, _) -> + Protocol. new_cr_format(#commit{ext=[]}=Cr) -> Cr; new_cr_format(#commit{ext=[{_,_}|_]}=Cr) -> Cr; @@ -2304,7 +2314,7 @@ reconfigure_participants(_, []) -> %% tell mnesia_tm on all involved nodes (including the local node) %% about the outcome. tell_outcome(Tid, Protocol, Node, CheckNodes, TellNodes) -> - Outcome = mnesia_recover:what_happened(Tid, Protocol, CheckNodes), + Outcome = mnesia_recover:what_happened(Tid, proto(Protocol), CheckNodes), case Outcome of aborted -> rpc:abcast(TellNodes, ?MODULE, {Tid,{do_abort, {mnesia_down, Node}}}); @@ -2313,6 +2323,9 @@ tell_outcome(Tid, Protocol, Node, CheckNodes, TellNodes) -> end, Outcome. +proto(sync_asym_trans) -> asym_trans; +proto(Proto) -> Proto. + do_stop(#state{coordinators = Coordinators}) -> Msg = {mnesia_down, node()}, lists:foreach(fun({Tid, _}) -> Tid#tid.pid ! Msg end, gb_trees:to_list(Coordinators)), diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl index 49bcec14af..c99158945d 100644 --- a/lib/mnesia/test/mnesia_isolation_test.erl +++ b/lib/mnesia/test/mnesia_isolation_test.erl @@ -29,7 +29,7 @@ -export([no_conflict/1, simple_queue_conflict/1, advanced_queue_conflict/1, simple_deadlock_conflict/1, advanced_deadlock_conflict/1, schema_deadlock/1, lock_burst/1, - nasty/1, basic_sticky_functionality/1, + nasty/1, basic_sticky_functionality/1, sticky_sync/1, unbound1/1, unbound2/1, create_table/1, delete_table/1, move_table_copy/1, add_table_index/1, del_table_index/1, transform_table/1, @@ -71,7 +71,8 @@ groups() -> advanced_deadlock_conflict, schema_deadlock, lock_burst, {group, sticky_locks}, {group, unbound_locking}, {group, admin_conflict}, nasty]}, - {sticky_locks, [], [basic_sticky_functionality]}, + {sticky_locks, [], + [basic_sticky_functionality,sticky_sync]}, {unbound_locking, [], [unbound1, unbound2]}, {admin_conflict, [], [create_table, delete_table, move_table_copy, @@ -594,9 +595,49 @@ get_held() -> mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, receive {mnesia_sticky_locks, Locks} -> Locks end. +sticky_sync(suite) -> []; +sticky_sync(Config) when is_list(Config) -> + %% BUG ERIERL-768 + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + + mnesia:create_table(dc, [{type, ordered_set}, {disc_copies, Nodes}]), + mnesia:create_table(ec, [{type, ordered_set}, {ram_copies, [N2]}]), + + TestFun = + fun(I) -> + %% In first transaction we initialise {dc, I} record with value 0 + First = fun() -> + %% Do a lot of writes into ram copies table + %% which on the Slave in do_commit will be + %% processed first + lists:foreach(fun(J) -> ok = mnesia:write(ec, {ec, J, 0}, write) end, + lists:seq(1, 750)), + %% Then set initial value of {dc, I} record to 0 with sticky_write + mnesia:write(dc, {dc, I, 0}, sticky_write) + end, + ok = mnesia:activity(transaction, First), + %% In second transaction we set value of {dc, I} record to 1 + Upd = fun() -> + %% Modify a single ram copies record with ensured lock grant + %% (key not used in previous transactions) + %% we use this second table only to force asym_trans protocol + mnesia:write(ec, {ec, 1001 + I, 0}, write), + %% And set final version of {dc, I} record to 1 with sticky_write + mnesia:write(dc, {dc, I, 1}, sticky_write) + end, + ok = mnesia:activity(transaction, Upd) + end, + + %% Fill 1000 dc records. At the end all dc records should have value 1. + lists:foreach(TestFun, lists:seq(1,1000)), + io:format("Written, check content~n",[]), + All = fun() -> mnesia:select(dc, [ {{dc, '_', 0}, [] ,['$_']} ]) end, + ?match({atomic, []}, rpc:call(N1, mnesia, sync_transaction, [All])), + ?match({atomic, []}, rpc:call(N2, mnesia, sync_transaction, [All])), -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + ?verify_mnesia(Nodes, []). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unbound1(suite) -> []; unbound1(Config) when is_list(Config) -> @@ -1036,29 +1077,57 @@ add_table_copy(Config) when is_list(Config) -> Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], ?match({atomic, ok}, mnesia:create_table(Tab, Def)), insert(Tab, 50), - {success, [A]} = ?start_activities([ThisNode]), + {success, [A]} = ?start_activities([ThisNode]), mnesia_test_lib:start_sync_transactions([A], 0), A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, ?match_receive({A, ok}), %% A is executed - Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, + Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, [Tab, Node2, ram_copies]]), - + ?match_receive(timeout), %% op waits for locks occupied by A A ! end_trans, %% Kill A, locks should be released - ?match_receive({A,{atomic,end_trans}}), - - receive + ?match_receive({A,{atomic,end_trans}}), + + receive Msg -> ?match({Pid, {atomic, ok}}, Msg) after timer:seconds(20) -> ?error("Operation timed out", []) end, + ?match_receive({'EXIT', Pid, normal}), sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async - ?match([], mnesia:system_info(held_locks)), - ?match([], mnesia:system_info(lock_queue)), + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + {atomic, ok} = mnesia:del_table_copy(Tab, Node2), + Self = self(), + New = spawn_link(Node2, + fun () -> + application:stop(mnesia), + Self ! {self(), ok}, + io:format(user, "restart mnesia~n", []), + Self ! {self(), catch application:start(mnesia)} + end), + receive {New,ok} -> ok end, + + Add = fun Add() -> + case mnesia:add_table_copy(Tab, Node2, disc_copies) of + {atomic, ok} -> ok; + _R -> io:format(user, "aborted with reason ~p~n", [_R]), + timer:sleep(10), + Add() + end + end, + + ?match(ok, Add()), + ?match_receive({New,ok}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), ok. del_table_copy(suite) -> []; diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt index a398ee0422..b169734f56 100755 --- a/lib/mnesia/test/mt +++ b/lib/mnesia/test/mt @@ -34,8 +34,35 @@ erlcmd="erl -sname a $p $args -mnesia_test_timeout" erlcmd1="erl -sname a1 $p $args" erlcmd2="erl -sname a2 $p $args" -xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 & -xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 & +if test z"$MT_TERM" = z ; then + MT_TERM=xterm +fi + +case $MT_TERM in + xterm) + geom0="-geometry 142x40+0+0" + geom1="-geometry 70x20+0+550" + geom2="-geometry 70x20+480+550" + title="-T" + exec="-e" + ;; + gnome-terminal) + geom0="--geometry 142x40+0+0" + geom1="--geometry 70x20+0+740" + geom2="--geometry 70x20+700+740" + title="--title" + exec="--hide-menubar --" + ;; + *rxvt) + geom0="-geometry 142x40+0+0" + geom1="-geometry 70x20+0+680" + geom2="-geometry 70x20+630+680" + title="-title" + exec="-e" +esac + +$MT_TERM $geom1 $title a1 $exec $erlcmd1 & +$MT_TERM $geom2 $title a2 $exec $erlcmd2 & rm "$latest" 2>/dev/null ln -s "$log" "$latest" @@ -51,11 +78,6 @@ echo "Give the following command in order to see the outcome from node a@$h"":" echo "" echo " less test_log$$" -ostype=`uname -s` -if [ "$ostype" = "SunOS" ] ; then - /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd & -else - xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" & -fi +$MT_TERM $geom0 $title a $exec script -f -c "$erlcmd" "$log" & tail -f "$log" | egrep 'Eval|<>ERROR|NYI' diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index 91d33474c8..819596b483 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -33,42 +33,43 @@ detail_pages() -> [{"Binary", fun init_bin_page/2}]. init_bin_page(Parent,{Type,Bin}) -> + Cs = observer_lib:colors(Parent), cdv_multi_wx:start_link( Parent, - [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}}, - {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}}, - {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}}, - {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin)}}, - {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}}, - {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}}, - {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}}, - {"Term",cdv_html_wx,{Type,binary_to_term_fun(Bin)}}]). + [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin,Cs)}}, + {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin,Cs)}}, + {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin,Cs)}}, + {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin,Cs)}}, + {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin,Cs)}}, + {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin,Cs)}}, + {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin,Cs)}}, + {"Term",cdv_html_wx,{Type,binary_to_term_fun(Bin,Cs)}}]). -format_bin_fun(Format,Bin) -> +format_bin_fun(Format,Bin,Cs) -> fun() -> try io_lib:format(Format,[Bin]) of - Str -> plain_html(lists:flatten(Str)) + Str -> plain_html(lists:flatten(Str),Cs) catch error:badarg -> Warning = "This binary cannot be formatted with " ++ Format, - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) end end. -binary_to_term_fun(Bin) -> +binary_to_term_fun(Bin,Cs) -> fun() -> try binary_to_term(Bin) of - Term -> plain_html(io_lib:format("~tp",[Term])) + Term -> plain_html(io_lib:format("~tp",[Term]),Cs) catch error:badarg -> Warning = "This binary cannot be converted to an Erlang term", - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) end end. -define(line_break,25). -hex_binary_fun(Bin) -> +hex_binary_fun(Bin,Cs) -> fun() -> S = "<<" ++ format_hex(Bin,?line_break) ++ ">>", - plain_html(io_lib:format("~s",[S])) + plain_html(io_lib:format("~s",[S]), Cs) end. format_hex(<<>>,_) -> @@ -82,5 +83,5 @@ format_hex(<<B1:4,B2:4,Bin/binary>>,N) -> [integer_to_list(B1,16),integer_to_list(B2,16),$, | format_hex(Bin,N-1)]. -plain_html(Text) -> - observer_html_lib:plain_page(Text). +plain_html(Text,Cs) -> + observer_html_lib:plain_page(Text,Cs). diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index 8956173c93..83ee98de6e 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -30,7 +30,8 @@ %% Records -record(state, - {panel, + {parent, + panel, app, %% which tool is the user expand_table, expand_wins=[], @@ -62,7 +63,7 @@ init(ParentWin, HtmlText, Tab, App) -> HtmlWin = observer_lib:html_window(ParentWin), wxHtmlWindow:setPage(HtmlWin,HtmlText), wx_misc:endBusyCursor(), - {HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}. + {HtmlWin, #state{parent=ParentWin, panel=HtmlWin,expand_table=Tab,app=App}}. init(ParentWin, Callback) -> {HtmlWin, State} = init(ParentWin, "", undefined, cdv), @@ -70,12 +71,15 @@ init(ParentWin, Callback) -> %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_info(active, #state{panel=HtmlWin,delayed_fetch=Callback}=State) +handle_info(active, #state{parent=Parent, panel=HtmlWin,delayed_fetch=Callback}=State) when Callback=/=undefined -> observer_lib:display_progress_dialog(HtmlWin, "Crashdump Viewer", "Reading data"), - {{expand,HtmlText,Tab},TW} = Callback:get_info(), + {{expand,Title,Info,Tab},TW} = Callback:get_info(), + Cs = observer_lib:colors(Parent), + HtmlText = observer_html_lib:expandable_term(Title,Info,Tab,Cs), + observer_lib:sync_destroy_progress_dialog(), wx_misc:beginBusyCursor(), wxHtmlWindow:setPage(HtmlWin,HtmlText), @@ -138,7 +142,8 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, list_to_integer(Key3)}}}, expand(Id,cdv_term_cb,State); _ when App =:= obs -> - observer ! {open_link, Target}; + observer ! {open_link, Target}, + State; _ -> cdv_virtual_list_wx:start_detail_win(Target), State diff --git a/lib/observer/src/cdv_mod_cb.erl b/lib/observer/src/cdv_mod_cb.erl index 2183e1aa3d..7f2cd0cf87 100644 --- a/lib/observer/src/cdv_mod_cb.erl +++ b/lib/observer/src/cdv_mod_cb.erl @@ -85,7 +85,8 @@ init_old_comp_page(Parent, Info) -> init_info_page(Parent, undefined) -> init_info_page(Parent, ""); init_info_page(Parent, String) -> - cdv_html_wx:start_link(Parent,observer_html_lib:plain_page(String)). + Cs = observer_lib:colors(Parent), + cdv_html_wx:start_link(Parent,observer_html_lib:plain_page(String,Cs)). format({Bin,q}) when is_binary(Bin) -> [$'|binary_to_list(Bin)]; diff --git a/lib/observer/src/cdv_persistent_cb.erl b/lib/observer/src/cdv_persistent_cb.erl index d5da18f7fc..90abc6a4f5 100644 --- a/lib/observer/src/cdv_persistent_cb.erl +++ b/lib/observer/src/cdv_persistent_cb.erl @@ -26,7 +26,4 @@ get_info() -> Tab = ets:new(pt_expand,[set,public]), {ok,PT,TW} = crashdump_viewer:persistent_terms(), - {{expand, - observer_html_lib:expandable_term("Persistent Terms",PT,Tab), - Tab}, - TW}. + {{expand, "Persistent Terms", PT, Tab}, TW}. diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl index 2497b4889e..61bd86f188 100644 --- a/lib/observer/src/cdv_proc_cb.erl +++ b/lib/observer/src/cdv_proc_cb.erl @@ -108,7 +108,7 @@ init_stack_page(Parent, Info) -> init_memory_page(Parent, Info0, Tag, Heading) -> Info = proplists:get_value(Tag,Info0), Tab = proplists:get_value(expand_table,Info0), - Html = observer_html_lib:expandable_term(Heading,Info,Tab), + Html = observer_html_lib:expandable_term(Heading,Info,Tab, observer_lib:colors(Parent)), cdv_html_wx:start_link(Parent,{expand,Html,Tab}). init_ets_page(Parent, Info) -> diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index 85da1d227a..a2a7a8750d 100644 --- a/lib/observer/src/cdv_term_cb.erl +++ b/lib/observer/src/cdv_term_cb.erl @@ -35,31 +35,32 @@ init_term_page(ParentWin, {Type, [Term, Tab]}) -> Expanded = expand(Term, true), BinSaved = expand(Term, Tab), observer_lib:report_progress({ok,stop_pulse}), + Cs = observer_lib:colors(ParentWin), cdv_multi_wx:start_link( ParentWin, - [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}}, - {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}}, - {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}}, - {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab)}}, - {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}}, - {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]). + [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab,Cs)}}, + {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab,Cs)}}, + {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab,Cs)}}, + {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab,Cs)}}, + {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab,Cs)}}, + {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab,Cs)}}]). -format_term_fun(Format,Term,Tab) -> +format_term_fun(Format,Term,Tab,Cs) -> fun() -> observer_lib:report_progress({ok,"Formatting term"}), observer_lib:report_progress({ok,start_pulse}), try io_lib:format(Format,[Term]) of - Str -> {expand, plain_html(Str), Tab} + Str -> {expand, plain_html(Str,Cs), Tab} catch error:badarg -> Warning = "This term cannot be formatted with " ++ Format, - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) after observer_lib:report_progress({ok,stop_pulse}) end end. -plain_html(Text) -> - observer_html_lib:plain_page(Text). +plain_html(Text,Cs) -> + observer_html_lib:plain_page(Text,Cs). expand(['#CDVBin',Offset,Size,Pos], true) -> {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index 14877b7eab..51e85e17c1 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -96,8 +96,9 @@ start_detail_win_2(Callback,Id) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([ParentWin, Callback, Owner]) -> - {Holder,TW} = spawn_table_holder(Callback, Owner), Panel = wxPanel:new(ParentWin), + Attrs = observer_lib:create_attrs(Panel), + {Holder,TW} = spawn_table_holder(Callback, Owner, Attrs), {Grid,MenuCols} = create_list_box(Panel, Holder, Callback, Owner), Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, @@ -233,7 +234,8 @@ handle_call(new_dump, _From, Ref = erlang:monitor(process,Holder), Holder ! stop, receive {'DOWN',Ref,_,_,_} -> ok end, - {NewHolder,TW} = spawn_table_holder(Callback, all), + Attrs = observer_lib:create_attrs(Grid), + {NewHolder,TW} = spawn_table_holder(Callback, all, Attrs), {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; handle_call(Msg, _From, State) -> @@ -329,9 +331,8 @@ handle_event(Event, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spawn_table_holder(Callback, Owner) -> +spawn_table_holder(Callback, Owner, Attrs) -> {Info,TW} = Callback:get_info(Owner), - Attrs = observer_lib:create_attrs(), Parent = self(), Holder = case Owner of diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 7100cc8790..8ad5da857e 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -197,8 +197,7 @@ setup(#state{frame=Frame, notebook=Notebook}=State) -> MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_wx, cdv_mem_cb), %% Persistent Terms Panel - PersistentPanel = add_page(Notebook, ?PERSISTENT_STR, - cdv_html_wx, cdv_persistent_cb), + PersistentPanel = add_page(Notebook, ?PERSISTENT_STR, cdv_html_wx, cdv_persistent_cb), %% Memory Panel IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_wx, cdv_int_tab_cb), diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 8c3eef5411..c4527ba063 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -48,7 +48,7 @@ usegc = false }). --record(paint, {font, pen, brush, sel, links}). +-record(paint, {font, fg, pen, brush, sel, links}). -record(app, {ptree, n2p, links, dim}). -record(box, {x,y, w,h, s1}). @@ -92,7 +92,8 @@ init([Notebook, Parent, _Config]) -> Extra = wxBoxSizer:new(?wxVERTICAL), DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA}, {style,?wxFULL_REPAINT_ON_RESIZE}]), - wxWindow:setBackgroundColour(DrawingArea, ?wxWHITE), + BG = wxWindow:getBackgroundColour(Apps), + wxWindow:setBackgroundStyle(DrawingArea, ?wxBG_STYLE_SYSTEM), wxWindow:setVirtualSize(DrawingArea, 800, 800), wxSplitterWindow:setMinimumPaneSize(Splitter,50), wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]), @@ -127,7 +128,17 @@ init([Notebook, Parent, _Config]) -> Font0 end, SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), - GreyBrush = wxBrush:new({230,230,240}), + {Fg,BGBrush,Pen} = + case observer_lib:is_darkmode(BG) of + false -> + {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT), + wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)), + wxPen:new({80,80,80}, [{width, Scale * 2}])}; + true -> + {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT), + wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)), + wxPen:new({0,0,0}, [{width, Scale * 2}])} + end, SelBrush = wxBrush:new(SelCol), LinkPen = wxPen:new(SelCol, [{width, Scale * 2}]), process_flag(trap_exit, true), @@ -137,8 +148,9 @@ init([Notebook, Parent, _Config]) -> app_w =DrawingArea, usegc = UseGC, paint=#paint{font = Font, - pen = wxPen:new({80,80,80}, [{width, Scale * 2}]), - brush= GreyBrush, + fg = Fg, + pen = Pen, + brush= BGBrush, sel = SelBrush, links= LinkPen } @@ -306,11 +318,11 @@ handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}}, handle_info({delivery, Pid, app, Curr, AppData}, State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC, - app_w=AppWin, paint=#paint{font=Font}}) -> + app_w=AppWin, paint=#paint{fg=Fg, font=Font}}) -> GC = if UseGC -> {?wxGC:create(AppWin), false}; true -> {false, wxWindowDC:new(AppWin)} end, - setFont(GC, Font, {0,0,0}), + setFont(GC, Font, Fg), App = build_tree(AppData, GC), destroy_gc(GC), setup_scrollbar(AppWin, App), @@ -508,13 +520,13 @@ tree_map([], _ , Acc) -> Acc. draw(_DC, undefined, _, _) -> ok; draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel, - #paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> + #paint{font=Font, fg=Fg, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> setPen(DC, LPen), [draw_xlink(Link, DC) || Link <- Links], setPen(DC, Pen), %% ?wxGC:drawRectangle(DC, 2,2, _W-2,_H-2), %% DEBUG setBrush(DC, Brush), - setFont(DC, Font, {0,0,0}), + setFont(DC, Font, Fg), draw_tree(Tree, root, DC), case Sel of undefined -> ok; diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 504d0877d9..7902b32cba 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -36,6 +36,7 @@ check = false }). +-record(colors, {fg, even, odd}). -record(attrs, {even, odd, searched, deleted, changed_odd, changed_even, new_odd, new_even}). -define(EVEN(Row), ((Row rem 2) =:= 0)). -define(BG_EVEN, {230,230,250}). diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index c67fa28c6d..4c92a8faab 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -24,9 +24,9 @@ %% viewer. No logic or states are kept by this module. %% --export([plain_page/1, - expandable_term/3, - warning/1]). +-export([plain_page/2, + expandable_term/4, + warning/2]). -include("crashdump_viewer.hrl"). -include("observer_defs.hrl"). @@ -34,8 +34,9 @@ %%%----------------------------------------------------------------- %%% Display the given information as is, no heading %%% Empty body if no info exists. -warning(Info) -> - header(body(warning_body(Info))). +warning(Info, Colors0) -> + Colors = convert(Colors0), + header(body(warning_body(Info), Colors)). warning_body(Info) -> [warn(Info)]. @@ -43,18 +44,22 @@ warning_body(Info) -> %%%----------------------------------------------------------------- %%% Display the given information as is, no heading %%% Empty body if no info exists. -plain_page(Info) -> - header(body(plain_body(Info))). +plain_page(Info, Colors0) -> + Colors = convert(Colors0), + header(body(plain_body(Info), Colors)). plain_body(Info) -> [pre(href_proc_port(lists:flatten(Info)))]. %%%----------------------------------------------------------------- %%% Expanded memory -expandable_term(Heading,Expanded,Tab) -> - header(Heading,body(expandable_term_body(Heading,Expanded,Tab))). +expandable_term(Heading,Expanded,Tab, Colors0) -> + Colors = convert(Colors0), + header(Heading, + body(expandable_term_body(Heading,Expanded,Tab,Colors), + Colors)). -expandable_term_body(Heading,[],_Tab) -> +expandable_term_body(Heading,[],_Tab, _) -> [case Heading of "MsgQueue" -> "No messages were found"; "Message Queue" -> "No messages were found"; @@ -65,7 +70,7 @@ expandable_term_body(Heading,[],_Tab) -> "SaslLog" -> "No log entry was found"; "Persistent Terms" -> "No persistent terms were found" end]; -expandable_term_body(Heading,Expanded,Tab) -> +expandable_term_body(Heading,Expanded,Tab, Colors) -> Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", [case Heading of "MsgQueue" -> @@ -74,7 +79,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=70%","Message"), th("WIDTH=30%","SeqTraceToken")]) | element(1, lists:mapfoldl(fun(Msg, Even) -> - {msgq_table(Tab, Msg, Even), + {msgq_table(Tab, Msg, Even, Colors), not Even} end, true, Expanded))]); @@ -84,7 +89,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=10%","Id"), th("WIDTH=90%","Message")]) | element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> - {msgq_table(Tab, Msg, N, Even), + {msgq_table(Tab, Msg, N, Even, Colors), {not Even, N+1}} end, {true,1}, Expanded))]); @@ -94,7 +99,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Term")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {stackdump_table(Tab, Entry, Even), + {stackdump_table(Tab, Entry, Even, Colors), not Even} end, true, Expanded))]); "ProcState" -> @@ -103,7 +108,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Information")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {proc_state(Tab, Entry,Even), + {proc_state(Tab, Entry,Even, Colors), not Even} end, true, Expanded))]); "SaslLog" -> @@ -115,37 +120,37 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=30%","Key"), th("WIDTH=70%","Value")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {dict_table(Tab, Entry,Even), + {dict_table(Tab, Entry, Even, Colors), not Even} end, true, Expanded))]) end]. -msgq_table(Tab,{Msg0,Token0}, Even) -> +msgq_table(Tab,{Msg0,Token0}, Even, Colors) -> Token = case Token0 of [] -> ""; _ -> io_lib:fwrite("~w",[Token0]) end, Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(pre(Msg)), td(Token)]). + tr(color(Even, Colors),[td(pre(Msg)), td(Token)]). -msgq_table(Tab,Msg0, Id, Even) -> +msgq_table(Tab,Msg0, Id, Even, Colors) -> Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). + tr(color(Even, Colors),[td(integer_to_list(Id)), td(pre(Msg))]). -stackdump_table(Tab,{Label0,Term0},Even) -> +stackdump_table(Tab,{Label0,Term0},Even, Colors) -> Label = io_lib:format("~w",[Label0]), Term = all_or_expand(Tab,Term0), - tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). + tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]). -dict_table(Tab,{Key0,Value0}, Even) -> +dict_table(Tab,{Key0,Value0}, Even, Colors) -> Key = all_or_expand(Tab,Key0), Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). + tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]). -proc_state(Tab,{Key0,Value0}, Even) -> +proc_state(Tab,{Key0,Value0}, Even, Colors) -> Key = lists:flatten(io_lib:format("~ts",[Key0])), Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). + tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]). all_or_expand(Tab,Term) -> Preview = io_lib:format("~tP",[Term,8]), @@ -171,8 +176,8 @@ all_or_expand(Tab,Bin,_PreviewStr,_Expand) Term = io_lib:format("~tp", [OBSBin]), href_proc_port(lists:flatten(Term), true). -color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); -color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). +color(true, #colors{even=Even}) -> "BGCOLOR="++Even; +color(false,#colors{odd=Odd}) -> "BGCOLOR="++Odd. %%%----------------------------------------------------------------- %%% Internal library @@ -180,10 +185,10 @@ start_html() -> "<HTML>\n". stop_html() -> "</HTML>". -start_html_body() -> - "<BODY BGCOLOR=\"#FFFFFF\">\n". +start_html_body(#colors{even=Even, fg=Fg}) -> + "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n". stop_html_body() -> - "</BODY>\n". + "</FONT> </BODY>\n". header(Body) -> header("","",Body). @@ -205,8 +210,8 @@ only_html_header(Title,JavaScript) -> JavaScript, "</HEAD>\n"]. -body(Text) -> - [start_html_body(), +body(Text, Colors) -> + [start_html_body(Colors), Text, stop_html_body()]. @@ -417,3 +422,8 @@ warn([]) -> []; warn(Warning) -> font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). + +convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) -> + #colors{fg = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]), + even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]), + odd = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}. diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 7c68b0ebb6..7d115306bd 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -28,8 +28,8 @@ interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1, display_info/2, display_info/3, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, - create_attrs/0, - set_listctrl_col_size/2, + is_darkmode/1, colors/1, create_attrs/1, + set_listctrl_col_size/2, mix/3, create_status_bar/1, html_window/1, html_window/2, make_obsbin/2, @@ -373,26 +373,44 @@ create_menu_item(separator, Menu, Index) -> wxMenu:insertSeparator(Menu, Index), Index+1. -create_attrs() -> - Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), +colors(Window) -> + DarkMode = is_darkmode(wxWindow:getBackgroundColour(Window)), Text = case wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT) of - {255,255,255,_} -> {10,10,10}; %% Is white on Mac for some reason - Color -> Color - end, - #attrs{even = wxListItemAttr:new(Text, ?BG_EVEN, Font), - odd = wxListItemAttr:new(Text, ?BG_ODD, Font), - deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), - changed_even = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_EVEN), Font), - changed_odd = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_ODD), Font), - new_even = wxListItemAttr:new(Text, mix(?BG_NEW,?BG_EVEN), Font), - new_odd = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_ODD), Font), - searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) - }. - -mix(RGB,_) -> RGB. - -%% mix({R,G,B},{MR,MG,MB}) -> -%% {trunc(R*MR/255), trunc(G*MG/255), trunc(B*MB/255)}. + {255,255,255,_} when not DarkMode -> {10,10,10}; %% Is white on Mac for some reason + Color -> Color + end, + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), + #colors{fg=rgb(Text), even=rgb(Even), odd=rgb(Odd)}. + +create_attrs(Window) -> + Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + #colors{fg=Text, even=Even, odd=Odd} = colors(Window), + #attrs{even = wxListItemAttr:new(Text, Even, Font), + odd = wxListItemAttr:new(Text, Odd, Font), + deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), + changed_even = wxListItemAttr:new(Text, mix(?BG_CHANGED, ?BG_EVEN, 0.9), Font), + changed_odd = wxListItemAttr:new(Text, mix(?BG_CHANGED, ?BG_ODD, 0.9), Font), + new_even = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_EVEN, 0.9), Font), + new_odd = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_ODD, 0.9), Font), + searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) + }. + +rgb({R,G,B,_}) -> {R,G,B}; +rgb({_,_,_}=RGB) -> RGB. + +mix(RGB,{MR,MG,MB,_}, V) -> + mix(RGB, {MR,MG,MB}, V); +mix({R,G,B,_}, RGB, V) -> + mix({R,G,B}, RGB, V); +mix({R,G,B},{MR,MG,MB}, V) when V =< 1.0 -> + {min(255, round(R*V+MR*(1.0-V))), + min(255, round(G*V+MG*(1.0-V))), + min(255, round(B*V+MB*(1.0-V)))}. + + +is_darkmode({R,G,B,_}) -> + ((R+G+B) div 3) < 100. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 79271addf2..50a6d6a915 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -55,7 +55,7 @@ -define(wxGC, wxGraphicsContext). --record(paint, {font, small, pen, pen2, pens, dot_pens, usegc = false}). +-record(paint, {font, small, fg, pen, pen2, pens, dot_pens, usegc = false}). start_link(Notebook, Parent, Config) -> wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). @@ -126,7 +126,16 @@ setup_graph_drawing(Panels) -> SF = wxFont:new(Scale * (DefSize-2), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), {F, SF} end, - BlackPen = wxPen:new({0,0,0}, [{width, Scale}]), + BG = wxWindow:getBackgroundColour((hd(Panels))#win.panel), + Fg = case observer_lib:is_darkmode(BG) of + false -> {0,0,0}; + true -> wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT) + end, + + PenColor = case observer_lib:is_darkmode(BG) of + false -> {0,0,0}; + true -> {0,0,0} + end, Pens = [wxPen:new(Col, [{width, Scale}, {style, ?wxSOLID}]) || Col <- tuple_to_list(colors())], DotPens = [wxPen:new(Col, [{width, Scale}, {style, ?wxDOT}]) @@ -134,8 +143,9 @@ setup_graph_drawing(Panels) -> #paint{usegc = UseGC, font = Font, small = SmallFont, - pen = ?wxGREY_PEN, - pen2 = BlackPen, + fg = Fg, %% Text color + pen = wxPen:new(PenColor), + pen2 = wxPen:new(PenColor, [{width, Scale}]), pens = list_to_tuple(Pens), dot_pens = list_to_tuple(DotPens) }. @@ -525,7 +535,7 @@ draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}}, DrawBs(), ok; -draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{small=Small}=Paint) -> +draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{fg=Fg, small=Small}=Paint) -> %% Draw Error Msg try draw_borders(DC, Ti, Win, Paint) of {X0,_Y0,DrawBs} -> @@ -533,7 +543,7 @@ draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{small=Small}=Paint) -> true -> "Waiting for data"; false -> "Information not available" end, - setFont(DC, Small, {0,0,0}), + setFont(DC, Small, Fg), {_,WW} = getSize(DC), drawText(DC, Text, X0 + 100, WW div 2), DrawBs(), @@ -628,7 +638,7 @@ spline_tan(Y0, Y1, Y2, Y3) -> draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, #win{name=Type, geom=Geom, info=Info, max={_,_,Unit,_}}, - #paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) -> + #paint{pen=Pen, pen2=Pen2, fg=Fg, font=Font, small=Small}) -> #{p0:={GraphX0, GraphY0}, p1:={GraphX1,GraphY1}, scale:={ScaleW0,_}, txsz:={TW,TH,SpaceW}, txt:={BottomTextY, MaxTextY}, strs:={Str1,Str2,Str3}} = Geom, @@ -640,7 +650,7 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2, GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4, - setFont(DC, Small, {0,0,0}), + setFont(DC, Small, Fg), Align = fun(Str, Y) -> {StrW, _} = getTextExtent(DC, Str), drawText(DC, Str, GraphX0 - StrW - ?BW, Y) @@ -670,11 +680,11 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50), strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75), - setFont(DC, Font, {0,0,0}), + setFont(DC, Font, Fg), Text = fun(X,Y, Str, PenId) -> if PenId == 0 -> - setFont(DC, Font, {0,0,0}); + setFont(DC, Font, Fg); PenId > 0 -> Id = 1 + ((PenId-1) rem tuple_size(colors())), setFont(DC, Font, element(Id, colors())) diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index 00cf1b5fba..5cb6d9bc22 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -61,7 +61,8 @@ inet}). -record(opt, {sort_key=2, - sort_incr=true + sort_incr=true, + odd_bg }). -record(state, @@ -111,7 +112,10 @@ init([Notebook, Parent, Config]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config}}. + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), + Opt = #opt{odd_bg=Odd}, + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config, opt=Opt}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -553,7 +557,7 @@ filter_monitor_info() -> update_grid(Grid, Sel, Opt, Ports) -> wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). -update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> +update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir, odd_bg=BG}, Ports) -> wxListCtrl:deleteAllItems(Grid), Update = fun(#port{id = Id, @@ -563,8 +567,8 @@ update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> controls = Ctrl}, Row) -> _Item = wxListCtrl:insertItem(Grid, Row, ""), - if (Row rem 2) =:= 0 -> - wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); + if (Row rem 2) =:= 1 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, BG); true -> ignore end, diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 4ab4a78462..6b359f3c44 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -94,7 +94,7 @@ start_link(Notebook, Parent, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, Parent, Config]) -> - Attrs = observer_lib:create_attrs(), + Attrs = observer_lib:create_attrs(Notebook), Self = self(), Acc = maps:get(acc, Config, false), Holder = spawn_link(fun() -> init_table_holder(Self, Acc, Attrs) end), diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index bd5fed0951..637a090a15 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -214,12 +214,14 @@ init_process_page(Panel, Pid) -> init_message_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]) of {messages, Messages} -> - Html = observer_html_lib:expandable_term("Message Queue", Messages, Table), + Html = observer_html_lib:expandable_term("Message Queue", Messages, + Table, Cs), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -230,11 +232,12 @@ init_message_page(Parent, Pid, Table) -> init_dict_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) of {dictionary,Dict} -> - Html = observer_html_lib:expandable_term("Dictionary", Dict, Table), + Html = observer_html_lib:expandable_term("Dictionary", Dict, Table, Cs), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -254,6 +257,8 @@ init_stack_page(Parent, Pid) -> wxListCtrl:insertColumn(LCtrl, 1, Li), wxListCtrl:setColumnWidth(LCtrl, 1, Scale * 300), wxListItem:destroy(Li), + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, current_stacktrace]) @@ -262,8 +267,8 @@ init_stack_page(Parent, Pid) -> wxListCtrl:deleteAllItems(LCtrl), wx:foldl(fun({M, F, A, Info}, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), - ?EVEN(Row) andalso - wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + ?EVEN(Row) orelse + wxListCtrl:setItemBackgroundColour(LCtrl, Row, Odd), wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), FileLine = case Info of [{file,File},{line,Line}] -> @@ -288,9 +293,10 @@ init_stack_page(Parent, Pid) -> init_state_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> StateInfo = fetch_state_info(Pid), - Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table), + Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table, Cs), wxHtmlWindow:setPage(Win, Html) end, Update(), @@ -341,6 +347,7 @@ fetch_state_info2(Pid, M) -> init_log_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> Fd = spawn_link(fun() -> io_server() end), rpc:call(node(Pid), rb, rescan, [[{start_log, Fd}]]), @@ -353,7 +360,7 @@ init_log_page(Parent, Pid, Table) -> NbBlanks = length(Pref) - 1, Re = "(<" ++ Pref ++ "\.[^>]{1,}>)[ ]{"++ integer_to_list(NbBlanks) ++ "}", Look = re:replace(ExpPid, Re, "\\1", [global, {return, list}]), - Html = observer_html_lib:expandable_term("SaslLog", Look, Table), + Html = observer_html_lib:expandable_term("SaslLog", Look, Table, Cs), wxHtmlWindow:setPage(Win, Html) end, Update(), diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 7bd67a0f0b..32d75f77d4 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -116,8 +116,8 @@ init([Parent, Opts]) -> TabId = table_id(Table), ColumnNames = column_names(Node, Source, TabId), KeyPos = key_pos(Node, Source, TabId), - - Attrs = observer_lib:create_attrs(), + Panel = wxPanel:new(Frame), + Attrs = observer_lib:create_attrs(Panel), Self = self(), Holder = spawn_link(fun() -> @@ -125,7 +125,6 @@ init([Parent, Opts]) -> length(ColumnNames), Node, Attrs) end), - Panel = wxPanel:new(Frame), Sizer = wxBoxSizer:new(?wxVERTICAL), Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, Grid = wxListCtrl:new(Panel, [{style, Style}, diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 9743a6ed42..d622b1423b 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -71,7 +71,7 @@ init([Notebook, Parent, Config]) -> Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, Self = self(), - Attrs = observer_lib:create_attrs(), + Attrs = observer_lib:create_attrs(Panel), Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), CBs = [{onGetItemText, fun(_, Item,Col) -> get_row(Holder, Item, Col) end}, {onGetItemAttr, fun(_, Item) -> get_attr(Holder, Item) end}], diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl index 84ed99afa5..10d88c994a 100644 --- a/lib/observer/test/crashdump_helper.erl +++ b/lib/observer/test/crashdump_helper.erl @@ -48,7 +48,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) -> Ref = make_ref(), Pid = self(), Bin = list_to_binary(lists:seq(1, 255)), - <<_:2,SubBin:17/binary,_/bits>> = Bin, + <<_:2,SubBin:65/binary,_/bits>> = Bin, register(named_port,Port), @@ -102,7 +102,7 @@ remote_proc(P1,Creator) -> end). create_binaries() -> - Sizes = lists:seq(60, 70) ++ lists:seq(120, 140), + Sizes = lists:seq(100, 120) ++ lists:seq(200, 240), [begin <<H:16/unit:8>> = erlang:md5(<<Size:32>>), Data = ((H bsl (8*150)) div (H+7919)), @@ -113,7 +113,7 @@ create_sub_binaries(Bins) -> [create_sub_binary(Bin, Start, LenSub) || Bin <- Bins, Start <- [0,1,2,3,4,5,10,22], - LenSub <- [0,1,2,3,4,6,9]]. + LenSub <- [0,1,2,3,4,6,9,65]]. create_sub_binary(Bin, Start, LenSub) -> Len = byte_size(Bin) - LenSub - Start, diff --git a/lib/os_mon/c_src/cpu_sup.c b/lib/os_mon/c_src/cpu_sup.c index c96a5c9f7c..98a2526aab 100644 --- a/lib/os_mon/c_src/cpu_sup.c +++ b/lib/os_mon/c_src/cpu_sup.c @@ -359,7 +359,7 @@ static cpu_t *read_procstat(FILE *fp, cpu_t *cpu) { memset(cpu, 0, sizeof(cpu_t)); return cpu; } - sscanf(buffer, "cpu%u %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", + sscanf(buffer, "cpu%u %llu %llu %llu %llu %llu %llu %llu %llu", &(cpu->id), &(cpu->user), &(cpu->nice_user), diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index 2cb0556001..7d5c6da2c8 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -22,7 +22,7 @@ -behaviour(supervisor). %% External exports --export([start_link/2]). +-export([start_link/2, stop/0, stop/1]). -export([start_sub_sup/1, start_master_sup/1]). -export([start_sub_agent/3, stop_sub_agent/1]). @@ -91,6 +91,39 @@ start_link(master, Opts, {takeover, Node}) -> Else end. + +stop() -> + stop(0). + +stop(Timeout) -> + case whereis(?SERVER) of + Pid when is_pid(Pid) -> + stop(Pid, Timeout); + _ -> + not_running + end. + +%% For some unfathomable reason there is no "nice" way to stop +%% a supervisor. The "normal" way to do it is: +%% 1) exit(Pid, kill) (kaboom) +%% 2) If the caller is the *parent*: exit(Pid, shutdown) +%% So, here we do it the really ugly way...but since this function is +%% intended for testing (mostly)... +stop(Pid, Timeout) when (Timeout =:= 0) -> + sys:terminate(Pid, shutdown), + ok; +stop(Pid, Timeout) -> + MRef = erlang:monitor(process, Pid), + sys:terminate(Pid, shutdown), + receive + {'DOWN', MRef, process, Pid, _} -> + ok + after Timeout -> + erlang:demonitor(MRef, [flush]), + {error, timeout} + end. + + get_own_loaded_mibs() -> AgentInfo = snmpa:info(snmp_master_agent), [ Name || {Name, _, _} <- loaded_mibs(AgentInfo) ]. diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl index cf8c95d69f..8e60cecaf9 100644 --- a/lib/snmp/src/manager/snmpm.erl +++ b/lib/snmp/src/manager/snmpm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2019. 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. @@ -32,7 +32,7 @@ %% Management API start/0, start/1, start_link/0, start_link/1, - stop/0, + stop/0, stop/1, monitor/0, demonitor/1, notify_started/1, cancel_notify_started/1, @@ -196,7 +196,12 @@ start(Opts) -> ok. stop() -> - snmpm_supervisor:stop(). + stop(0). + +stop(Timeout) when (Timeout =:= infinity) orelse + (is_integer(Timeout) andalso (Timeout >= 0)) -> + snmpm_supervisor:stop(Timeout). + monitor() -> diff --git a/lib/snmp/src/manager/snmpm_supervisor.erl b/lib/snmp/src/manager/snmpm_supervisor.erl index c36bbe1bdd..bc66025c6f 100644 --- a/lib/snmp/src/manager/snmpm_supervisor.erl +++ b/lib/snmp/src/manager/snmpm_supervisor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% Copyright Ericsson AB 2004-2019. 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. @@ -24,7 +24,7 @@ %% External exports --export([start_link/2, stop/0]). +-export([start_link/2, stop/0, stop/1]). %% supervisor callbacks -export([init/1]). @@ -38,26 +38,54 @@ %%%------------------------------------------------------------------- %%% API %%%------------------------------------------------------------------- + start_link(Type, Opts) -> ?d("start_link -> entry with" "~n Opts: ~p", [Opts]), SupName = {local, ?MODULE}, supervisor:start_link(SupName, ?MODULE, [Type, Opts]). + stop() -> + stop(0). + +stop(Timeout) -> ?d("stop -> entry", []), case whereis(?SERVER) of Pid when is_pid(Pid) -> - ?d("stop -> Pid: ~p", [Pid]), - exit(Pid, shutdown), - ?d("stop -> stopped", []), - ok; + stop(Pid, Timeout); _ -> ?d("stop -> not running", []), not_running end. +%% For some unfathomable reason there is no "nice" way to stop +%% a supervisor. The "normal" way to do it is: +%% 1) exit(Pid, kill) (kaboom) +%% 2) If the caller is the *parent*: exit(Pid, shutdown) +%% So, here we do it the really ugly way...but since this function is +%% intended for testing (mostly)... +stop(Pid, Timeout) when (Timeout =:= 0) -> + ?d("stop -> Pid: ~p", [Pid]), + sys:terminate(Pid, shutdown), + ?d("stop -> stopped", []), + ok; +stop(Pid, Timeout) -> + ?d("stop -> Pid: ~p", [Pid]), + MRef = erlang:monitor(process, Pid), + sys:terminate(Pid, shutdown), + receive + {'DOWN', MRef, process, Pid, _} -> + ?d("stop -> stopped", []), + ok + after Timeout -> + ?d("stop -> timeout", []), + erlang:demonitor(MRef, [flush]), + {error, timeout} + end. + + %%%------------------------------------------------------------------- %%% Callback functions from supervisor %%%------------------------------------------------------------------- diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl index 615d6774b9..c0da47dc4c 100644 --- a/lib/snmp/test/snmp_agent_test_lib.erl +++ b/lib/snmp/test/snmp_agent_test_lib.erl @@ -423,7 +423,18 @@ tc_wait(From, Env, M, F, A) -> "~n ~p" "~n", [Res]), From ! {tc_runner_done, self(), Res, get(test_server_loc)}, - exit(Res). + %% The point of this is that in some cases we have seen that the + %% exit signal having been "passed on" to the CT, which consider any + %% exit a fail (even if its {'EXIT', ok}). + %% So, just to be on the safe side, convert an 'ok' to a 'normal'. + case Res of + ok -> + exit(normal); + {ok, _} -> + exit(normal); + _ -> + exit(Res) + end. tc_run(Mod, Func, Args, Opts) -> ?PRINT2("tc_run -> entry with" diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 7cd3eae0c7..c31bb92e1f 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -62,7 +62,7 @@ register_user1/1, - register_agent1/1, + register_agent_old/1, register_agent2/1, register_agent3/1, @@ -185,7 +185,6 @@ end_per_suite(Config) when is_list(Config) -> init_per_testcase(Case, Config) when is_list(Config) -> - io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), p(Case, "init_per_testcase begin when" "~n Nodes: ~p~n~n", [erlang:nodes()]), %% This version of the API, based on Addr and Port, has been deprecated @@ -204,13 +203,15 @@ init_per_testcase(Case, Config) when is_list(Config) -> Result = case lists:member(Case, DeprecatedApiCases) of true -> - {skip, api_no_longer_supported}; + {skip, "API no longer supported"}; false -> try init_per_testcase2(Case, Config) catch - C:{skip, _} = E:_ when ((C =:= throw) orelse (C =:= exit)) -> + C:{skip, _} = E:_ when ((C =:= throw) orelse + (C =:= exit)) -> E; - C:E:_ when ((C =:= throw) orelse (C =:= exit)) -> + C:E:_ when ((C =:= throw) orelse + (C =:= exit)) -> {skip, {catched, C, E}} end end, @@ -479,7 +480,7 @@ groups() -> }, {agent_tests, [], [ - register_agent1, + register_agent_old, register_agent2, register_agent3 ] @@ -595,7 +596,7 @@ groups() -> ipv6_tests() -> [ - register_agent1, + register_agent_old, simple_sync_get_next3, simple_async_get2, simple_sync_get3, @@ -671,11 +672,11 @@ end_per_group(_GroupName, Config) -> simple_start_and_stop1(suite) -> []; simple_start_and_stop1(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented), - process_flag(trap_exit, true), - put(tname,ssas1), - p("starting with Config: ~n~p", [Config]), + ?TC_TRY(simple_start_and_stop1, + fun() -> do_simple_start_and_stop1(Config) end). +do_simple_start_and_stop1(Config) -> + p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), DbDir = ?config(manager_db_dir, Config), @@ -696,7 +697,6 @@ simple_start_and_stop1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -704,9 +704,10 @@ simple_start_and_stop1(Config) when is_list(Config) -> simple_start_and_stop2(suite) -> []; simple_start_and_stop2(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented), - process_flag(trap_exit, true), - put(tname,ssas2), + ?TC_TRY(simple_start_and_stop2, + fun() -> do_simple_start_and_stop2(Config) end). + +do_simple_start_and_stop2(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -744,7 +745,6 @@ simple_start_and_stop2(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -752,8 +752,10 @@ simple_start_and_stop2(Config) when is_list(Config) -> simple_start_and_monitor_crash1(suite) -> []; simple_start_and_monitor_crash1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ssamc1), + ?TC_TRY(simple_start_and_monitor_crash1, + fun() -> do_simple_start_and_monitor_crash1(Config) end). + +do_simple_start_and_monitor_crash1(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -797,7 +799,6 @@ simple_start_and_monitor_crash1(Config) when is_list(Config) -> ?FAIL(timeout) end, - p("end"), ok. @@ -805,8 +806,10 @@ simple_start_and_monitor_crash1(Config) when is_list(Config) -> simple_start_and_monitor_crash2(suite) -> []; simple_start_and_monitor_crash2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ssamc2), + ?TC_TRY(simple_start_and_monitor_crash2, + fun() -> do_simple_start_and_monitor_crash2(Config) end). + +do_simple_start_and_monitor_crash2(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -851,7 +854,6 @@ simple_start_and_monitor_crash2(Config) when is_list(Config) -> ?FAIL(timeout) end, - p("end"), ok. @@ -897,8 +899,10 @@ simulate_crash(NumKills, _) -> notify_started01(suite) -> []; notify_started01(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ns01), + ?TC_TRY(notify_started01, + fun() -> do_notify_started01(Config) end). + +do_notify_started01(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -985,37 +989,34 @@ snmpm_starter(Opts, To) -> notify_started02(suite) -> []; notify_started02(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ns02), + ?TC_TRY(notify_started02, + fun() -> notify_started02_cond(Config) end, + fun() -> do_notify_started02(Config) end). - %% <CONDITIONAL-SKIP> - %% The point of this is to catch machines running - %% SLES9 (2.6.5) +notify_started02_cond(Config) -> LinuxVersionVerify = fun() -> case os:cmd("uname -m") of "i686" ++ _ -> -%% io:format("found an i686 machine, " -%% "now check version~n", []), case os:version() of {2, 6, Rev} when Rev >= 16 -> - true; + false; {2, Min, _} when Min > 6 -> - true; + false; {Maj, _, _} when Maj > 2 -> - true; + false; _ -> - false + true end; _ -> - true + false end end, Skippable = [{unix, [{linux, LinuxVersionVerify}]}], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, - ?NON_PC_TC_MAYBE_SKIP(Config, Condition), - %% </CONDITIONAL-SKIP> - + ?NON_PC_TC_MAYBE_SKIP(Config, Condition). + +do_notify_started02(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -1023,10 +1024,10 @@ notify_started02(Config) when is_list(Config) -> write_manager_conf(ConfDir), - Opts = [{server, [{verbosity, log}]}, - {net_if, [{verbosity, silence}]}, + Opts = [{server, [{verbosity, log}]}, + {net_if, [{verbosity, silence}]}, {note_store, [{verbosity, silence}]}, - {config, [{verbosity, log}, {dir, ConfDir}, {db_dir, DbDir}]}], + {config, [{verbosity, debug}, {dir, ConfDir}, {db_dir, DbDir}]}], p("start snmpm client process"), NumIterations = 5, @@ -1056,8 +1057,14 @@ notify_started02(Config) when is_list(Config) -> p("await snmpm client process exit (max ~p+10000 msec)", [ApproxStartTime]), receive + %% We take this opportunity to check if we got a skip from + %% the ctrl process. + {'EXIT', Pid2, {skip, SkipReason1}} -> + ?SKIP(SkipReason1); {'EXIT', Pid1, normal} -> ok; + {'EXIT', Pid1, {suite_failed, Reason1}} -> + ?FAIL({client, Reason1}); {'EXIT', Pid1, Reason1} -> ?FAIL({client, Reason1}) after ApproxStartTime + 10000 -> @@ -1070,6 +1077,9 @@ notify_started02(Config) when is_list(Config) -> receive {'EXIT', Pid2, normal} -> ok; + {'EXIT', Pid2, {skip, SkipReason2}} -> + %% In case of a race + ?SKIP(SkipReason2); {'EXIT', Pid2, Reason2} -> ?FAIL({ctrl, Reason2}) after 5000 -> @@ -1094,7 +1104,7 @@ ns02_client_await_approx_runtime(Pid) -> "~n ~p", [Pid, Reason]), {error, Reason} - after 15000 -> + after 30000 -> %% Either something is *really* wrong or this machine %% is dog slow. Either way, this is a skip-reason... {skip, approx_runtime_timeout} @@ -1159,6 +1169,12 @@ ns02_ctrl(Opts, N) -> p("starting"), ns02_ctrl_loop(Opts, N). + +%% We have seen that some times it takes unreasonably long time to +%% start the manager (it got "stuck" in snmpm_config). But since +%% we did not have enough verbosity, we do not know how far it got. +%% So, we try to monitor each start attempt. We allow 5 sec (just +%% to give slow boxes a chance). ns02_ctrl_loop(_Opts, 0) -> p("done"), exit(normal); @@ -1166,19 +1182,45 @@ ns02_ctrl_loop(Opts, N) -> p("entry when N: ~p", [N]), ?SLEEP(2000), p("start manager"), - snmpm:start(Opts), + TS1 = erlang:system_time(millisecond), + {StarterPid, StarterMRef} = + erlang:spawn_monitor(fun() -> exit(snmpm:start(Opts)) end), + receive + {'DOWN', StarterMRef, process, StarterPid, ok} -> + TS2 = erlang:system_time(millisecond), + p("manager started: ~w ms", [TS2-TS1]), + ok + after 5000 -> + p("manager (~p) start timeout - kill", [StarterPid]), + exit(StarterPid, kill), + exit({skip, start_timeout}) + end, ?SLEEP(2000), p("stop manager"), - snmpm:stop(), + ?SLEEP(100), % Give the verbosity to take effect... + TS3 = erlang:system_time(millisecond), + case snmpm:stop(5000) of + ok -> + TS4 = erlang:system_time(millisecond), + p("manager stopped: ~p ms", [TS4-TS3]), + ok; + {error, timeout} -> + p("manager stop timeout - kill (cleanup) and skip"), + exit(whereis(snmpm_supervisor), kill), + exit({skip, stop_timeout}) + end, ns02_ctrl_loop(Opts, N-1). + %%====================================================================== info(suite) -> []; info(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,info), + ?TC_TRY(info, + fun() -> do_info(Config) end). + +do_info(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -1206,7 +1248,6 @@ info(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. verify_info(Info) when is_list(Info) -> @@ -1246,9 +1287,10 @@ verify_info([{Key, SubKeys}|Keys], Info) -> register_user1(suite) -> []; register_user1(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented). - process_flag(trap_exit, true), - put(tname,ru1), + ?TC_TRY(register_user1, + fun() -> do_register_user1(Config) end). + +do_register_user1(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1322,7 +1364,6 @@ register_user1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. verify_users([], []) -> @@ -1340,14 +1381,15 @@ verify_users(ActualUsers0, [User|RegUsers]) -> %%====================================================================== -register_agent1(doc) -> +register_agent_old(doc) -> ["Test registration of agents with the OLD interface functions"]; -register_agent1(suite) -> +register_agent_old(suite) -> []; -register_agent1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ra1), - +register_agent_old(Config) when is_list(Config) -> + ?TC_TRY(register_agent_old, + fun() -> do_register_agent_old(Config) end). + +do_register_agent_old(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1462,7 +1504,6 @@ register_agent1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1473,8 +1514,10 @@ register_agent2(doc) -> register_agent2(suite) -> []; register_agent2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ra2), + ?TC_TRY(register_agent2, + fun() -> do_register_agent2(Config) end). + +do_register_agent2(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1607,7 +1650,6 @@ register_agent2(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1619,8 +1661,10 @@ register_agent3(doc) -> register_agent3(suite) -> []; register_agent3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ra3), + ?TC_TRY(register_agent3, + fun() -> do_register_agent3(Config) end). + +do_register_agent3(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1757,7 +1801,6 @@ register_agent3(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1766,10 +1809,11 @@ register_agent3(Config) when is_list(Config) -> simple_sync_get1(doc) -> ["Simple sync get-request - Old style (Addr & Port)"]; simple_sync_get1(suite) -> []; simple_sync_get1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get1(Config) end). - process_flag(trap_exit, true), - put(tname, ssg1), +do_simple_sync_get1(Config) -> p("starting with Config: ~p~n", [Config]), Node = ?config(manager_node, Config), @@ -1828,18 +1872,18 @@ simple_sync_get2(doc) -> ["Simple sync get-request - Version 2 API (TargetName)"]; simple_sync_get2(suite) -> []; simple_sync_get2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssg2), - do_simple_sync_get2(Config), - display_log(Config), - ok. + ?TC_TRY(simple_sync_get2, + fun() -> do_simple_sync_get2(Config) end). do_simple_sync_get2(Config) -> + p("starting with Config: ~n~p", [Config]), Get = fun(Node, TargetName, Oids) -> mgr_user_sync_get(Node, TargetName, Oids) end, PostVerify = fun() -> ok end, - do_simple_sync_get2(Config, Get, PostVerify). + do_simple_sync_get2(Config, Get, PostVerify), + display_log(Config), + ok. do_simple_sync_get2(Config, Get, PostVerify) -> p("starting with Config: ~p~n", [Config]), @@ -1895,13 +1939,11 @@ simple_sync_get3(doc) -> ["Simple sync get-request - Version 3 API (TargetName and send-opts)"]; simple_sync_get3(suite) -> []; simple_sync_get3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssg3), - do_simple_sync_get3(Config), - display_log(Config), - ok. + ?TC_TRY(simple_sync_get3, + fun() -> do_simple_sync_get3(Config) end). do_simple_sync_get3(Config) -> + p("starting with Config: ~n~p", [Config]), Self = self(), Msg = simple_sync_get3, Fun = fun() -> Self ! Msg end, @@ -1920,7 +1962,9 @@ do_simple_sync_get3(Config) -> ok end end, - do_simple_sync_get2(Config, Get, PostVerify). + do_simple_sync_get2(Config, Get, PostVerify), + display_log(Config), + ok. %%====================================================================== @@ -1929,10 +1973,11 @@ simple_async_get1(doc) -> ["Simple (async) get-request - Old style (Addr & Port)"]; simple_async_get1(suite) -> []; simple_async_get1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get1(Config) end). - process_flag(trap_exit, true), - put(tname, sag1), +do_simple_async_get1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2033,8 +2078,10 @@ simple_async_get2(doc) -> ["Simple (async) get-request - Version 2 API (TargetName)"]; simple_async_get2(suite) -> []; simple_async_get2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sag2), + ?TC_TRY(simple_async_get2, + fun() -> do_simple_async_get2(Config) end). + +do_simple_async_get2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), @@ -2114,8 +2161,10 @@ simple_async_get3(doc) -> ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"]; simple_async_get3(suite) -> []; simple_async_get3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sag3), + ?TC_TRY(simple_async_get3, + fun() -> do_simple_async_get3(Config) end). + +do_simple_async_get3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), @@ -2146,10 +2195,11 @@ simple_sync_get_next1(doc) -> ["Simple (sync) get_next-request - " "Old style (Addr & Port)"]; simple_sync_get_next1(suite) -> []; simple_sync_get_next1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get_next1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get_next1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgn1), +do_simple_sync_get_next1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2287,8 +2337,10 @@ simple_sync_get_next2(doc) -> ["Simple (sync) get_next-request - Version 2 API (TargetName)"]; simple_sync_get_next2(suite) -> []; simple_sync_get_next2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_sync_get_next2, + fun() -> do_simple_sync_get_next2(Config) end). + +do_simple_sync_get_next2(Config) -> p("starting with Config: ~p~n", [Config]), GetNext = fun(Node, TargetName, Oids) -> @@ -2440,10 +2492,11 @@ simple_async_get_next1(doc) -> ["Simple (async) get_next-request - " "Old style (Addr & Port)"]; simple_async_get_next1(suite) -> []; simple_async_get_next1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get_next1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get_next1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgn1), +do_simple_async_get_next1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2540,8 +2593,10 @@ simple_async_get_next2(doc) -> ["Simple (async) get_next-request - Version 2 API (TargetName)"]; simple_async_get_next2(suite) -> []; simple_async_get_next2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_async_get_next2, + fun() -> do_simple_async_get_next2(Config) end). + +do_simple_async_get_next2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2651,8 +2706,10 @@ simple_async_get_next3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_async_get_next3(suite) -> []; simple_async_get_next3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_async_get_next3, + fun() -> do_simple_async_get_next3(Config) end). + +do_simple_async_get_next3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2694,10 +2751,11 @@ simple_sync_set1(doc) -> ["Simple (sync) set-request - " "Old style (Addr & Port)"]; simple_sync_set1(suite) -> []; simple_sync_set1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_set1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_set1(Config) end). - process_flag(trap_exit, true), - put(tname, sss1), +do_simple_sync_set1(Config) -> p("starting with Config: ~p~n", [Config]), Node = ?config(manager_node, Config), @@ -2767,8 +2825,10 @@ simple_sync_set2(doc) -> ["Simple (sync) set-request - Version 2 API (TargetName)"]; simple_sync_set2(suite) -> []; simple_sync_set2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sss2), + ?TC_TRY(simple_sync_set2, + fun() -> do_simple_sync_set2(Config) end). + +do_simple_sync_set2(Config) -> p("starting with Config: ~p~n", [Config]), Set = fun(Node, TargetName, VAVs) -> @@ -2837,8 +2897,10 @@ simple_sync_set3(doc) -> ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"]; simple_sync_set3(suite) -> []; simple_sync_set3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sss3), + ?TC_TRY(simple_sync_set3, + fun() -> do_simple_sync_set3(Config) end). + +do_simple_sync_set3(Config) -> p("starting with Config: ~p~n", [Config]), Self = self(), @@ -2866,10 +2928,11 @@ simple_async_set1(doc) -> ["Simple (async) set-request - " "Old style (Addr & Port)"]; simple_async_set1(suite) -> []; simple_async_set1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_set1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_set1(Config) end). - process_flag(trap_exit, true), - put(tname, sas1), +do_simple_async_set1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2961,8 +3024,10 @@ simple_async_set2(doc) -> ["Simple (async) set-request - Version 2 API (TargetName)"]; simple_async_set2(suite) -> []; simple_async_set2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sas2), + ?TC_TRY(simple_async_set2, + fun() -> do_simple_async_set2(Config) end). + +do_simple_async_set2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3034,8 +3099,10 @@ simple_async_set3(doc) -> ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; simple_async_set3(suite) -> []; simple_async_set3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sas3), + ?TC_TRY(simple_async_set3, + fun() -> do_simple_async_set3(Config) end). + +do_simple_async_set3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3078,10 +3145,11 @@ simple_sync_get_bulk1(doc) -> ["Simple (sync) get_bulk-request - " "Old style (Addr & Port)"]; simple_sync_get_bulk1(suite) -> []; simple_sync_get_bulk1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get_bulk1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get_bulk1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgb1), +do_simple_sync_get_bulk1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3252,8 +3320,10 @@ simple_sync_get_bulk2(doc) -> ["Simple (sync) get_bulk-request - Version 2 API (TargetName)"]; simple_sync_get_bulk2(suite) -> []; simple_sync_get_bulk2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgb2), + ?TC_TRY(simple_sync_get_bulk2, + fun() -> do_simple_sync_get_bulk2(Config) end). + +do_simple_sync_get_bulk2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3408,8 +3478,10 @@ simple_sync_get_bulk3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_sync_get_bulk3(suite) -> []; simple_sync_get_bulk3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgb3), + ?TC_TRY(simple_sync_get_bulk3, + fun() -> do_simple_sync_get_bulk3(Config) end). + +do_simple_sync_get_bulk3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3445,10 +3517,11 @@ simple_async_get_bulk1(doc) -> ["Simple (async) get_bulk-request - " "Old style (Addr & Port)"]; simple_async_get_bulk1(suite) -> []; simple_async_get_bulk1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get_bulk1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get_bulk1(Config) end). - process_flag(trap_exit, true), - put(tname, sagb1), +do_simple_async_get_bulk1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3591,8 +3664,10 @@ simple_async_get_bulk2(doc) -> ["Simple (async) get_bulk-request - Version 2 API (TargetName)"]; simple_async_get_bulk2(suite) -> []; simple_async_get_bulk2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sagb2), + ?TC_TRY(simple_async_get_bulk2, + fun() -> do_simple_async_get_bulk2(Config) end). + +do_simple_async_get_bulk2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3747,8 +3822,10 @@ simple_async_get_bulk3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_async_get_bulk3(suite) -> []; simple_async_get_bulk3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sagb3), + ?TC_TRY(simple_async_get_bulk3, + fun() -> do_simple_async_get_bulk3(Config) end). + +do_simple_async_get_bulk3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3791,10 +3868,11 @@ misc_async1(doc) -> ["Misc (async) request(s) - " "Old style (Addr & Port)"]; misc_async1(suite) -> []; misc_async1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(misc_async1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_misc_async1(Config) end). - process_flag(trap_exit, true), - put(tname, ms1), +do_misc_async1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3983,8 +4061,10 @@ misc_async2(doc) -> ["Misc (async) request(s) - Version 2 API (TargetName)"]; misc_async2(suite) -> []; misc_async2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ms2), + ?TC_TRY(misc_async2, + fun() -> do_misc_async2(Config) end). + +do_misc_async2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4233,8 +4313,10 @@ verify_trap(Trap, [{Id, Verifier}|Verifiers]) -> trap1(suite) -> []; trap1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,t1), + ?TC_TRY(trap1, + fun() -> do_trap1(Config) end). + +do_trap1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4386,8 +4468,10 @@ trap1(Config) when is_list(Config) -> trap2(suite) -> []; trap2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,t2), + ?TC_TRY(trap2, + fun() -> do_trap2(Config) end). + +do_trap2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4579,8 +4663,10 @@ trap2(Config) when is_list(Config) -> inform1(suite) -> []; inform1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i1), + ?TC_TRY(inform1, + fun() -> do_inform1(Config) end). + +do_inform1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4706,8 +4792,10 @@ inform1(Config) when is_list(Config) -> inform2(suite) -> []; inform2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, i2), + ?TC_TRY(inform2, + fun() -> do_inform2(Config) end). + +do_inform2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4878,8 +4966,10 @@ inform2(Config) when is_list(Config) -> inform3(suite) -> []; inform3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i3), + ?TC_TRY(inform3, + fun() -> do_inform3(Config) end). + +do_inform3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -5014,8 +5104,10 @@ inform3(Config) when is_list(Config) -> inform4(suite) -> []; inform4(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i4), + ?TC_TRY(inform4, + fun() -> do_inform4(Config) end). + +do_inform4(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -5134,8 +5226,10 @@ inform4(Config) when is_list(Config) -> inform_swarm(suite) -> []; inform_swarm(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, is), + ?TC_TRY(inform_swarm, + fun() -> do_inform_swarm(Config) end). + +do_inform_swarm(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -5306,8 +5400,10 @@ report(Config) when is_list(Config) -> otp8015_1(doc) -> ["OTP-8015:1 - testing the new api-function."]; otp8015_1(suite) -> []; otp8015_1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, otp8015_1), + ?TC_TRY(otp8015_1, + fun() -> do_otp8015_1(Config) end). + +do_otp8015_1(Config) -> p("starting with Config: ~p~n", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -5354,8 +5450,10 @@ otp8015_1(Config) when is_list(Config) -> otp8395_1(doc) -> ["OTP-8395:1 - simple get with ATL sequence numbering."]; otp8395_1(suite) -> []; otp8395_1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, otp8395_1), + ?TC_TRY(otp8395_1, + fun() -> do_otp8395_1(Config) end). + +do_otp8395_1(Config) -> do_simple_sync_get2(Config). diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl index 35bc535a80..26b68501e2 100644 --- a/lib/snmp/test/snmp_test_lib.erl +++ b/lib/snmp/test/snmp_test_lib.erl @@ -23,6 +23,7 @@ -include_lib("kernel/include/file.hrl"). +-export([tc_try/2, tc_try/3]). -export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1, display_suite_info/1]). -export([non_pc_tc_maybe_skip/4, os_based_skip/1, @@ -43,10 +44,107 @@ -export([watchdog/3, watchdog_start/1, watchdog_start/2, watchdog_stop/1]). -export([del_dir/1]). -export([cover/1]). --export([p/2, print1/2, print2/2, print/5, formated_timestamp/0]). +-export([f/2, p/2, print1/2, print2/2, print/5, formated_timestamp/0]). %% ---------------------------------------------------------------------- +%% Run test-case +%% + +%% *** tc_try/2,3 *** +%% Case: Basically the test case name +%% TCCondFun: A fun that is evaluated before the actual test case +%% The point of this is that it can performs checks to +%% see if we shall run the test case at all. +%% For instance, the test case may only work in specific +%% conditions. +%% FCFun: The test case fun +tc_try(Case, TCFun) -> + tc_try(Case, fun() -> ok end, TCFun). + +tc_try(Case, TCCondFun, TCFun) + when is_atom(Case) andalso + is_function(TCCondFun, 0) andalso + is_function(TCFun, 0) -> + tc_begin(Case), + try TCCondFun() of + ok -> + try + begin + TCFun(), + sleep(seconds(1)), + tc_end("ok") + end + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + tc_end( f("skipping(catched,~w,tc)", [C]) ), + SKIP; + C:E:S -> + tc_end( f("failed(catched,~w,tc)", [C]) ), + erlang:raise(C, E, S) + end; + {skip, _} = SKIP -> + tc_end("skipping(tc)"), + SKIP; + {error, Reason} -> + tc_end("failed(tc)"), + exit({tc_cond_failed, Reason}) + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + tc_end( f("skipping(catched,~w,cond)", [C]) ), + SKIP; + C:E:S -> + tc_end( f("failed(catched,~w,cond)", [C]) ), + erlang:raise(C, E, S) + end. + + +tc_set_name(N) when is_atom(N) -> + tc_set_name(atom_to_list(N)); +tc_set_name(N) when is_list(N) -> + put(tc_name, N). + +tc_get_name() -> + get(tc_name). + +tc_begin(TC) -> + OldVal = process_flag(trap_exit, true), + put(old_trap_exit, OldVal), + tc_set_name(TC), + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). + +tc_end(Result) when is_list(Result) -> + OldVal = erase(old_trap_exit), + process_flag(trap_exit, OldVal), + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), + ok. + +tc_print(F, Before, After) -> + tc_print(F, [], Before, After). + +tc_print(F, A, Before, After) -> + Name = tc_which_name(), + FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", + [formated_timestamp(),Name,self()|A]), + io:format(user, Before ++ FStr ++ After, []). + +tc_which_name() -> + case tc_get_name() of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end. + + +%% ---------------------------------------------------------------------- %% Misc functions %% @@ -202,11 +300,14 @@ non_pc_tc_maybe_skip(Config, Condition, File, Line) %% test-server... ok; _ -> - case Condition() of + try Condition() of true -> skip(non_pc_testcase, File, Line); false -> ok + catch + C:E:S -> + skip({condition, C, E, S}, File, Line) end end end. @@ -712,6 +813,9 @@ cover([Suite, Case] = Args) when is_atom(Suite) andalso is_atom(Case) -> %% (debug) Print functions %% +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + p(Mod, Case) when is_atom(Mod) andalso is_atom(Case) -> case get(test_case) of undefined -> diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl index 99b86a928c..f077f15d3e 100644 --- a/lib/snmp/test/snmp_test_lib.hrl +++ b/lib/snmp/test/snmp_test_lib.hrl @@ -18,15 +18,11 @@ %% The Initial Developer of the Original Code is Ericsson AB. %% %CopyrightEnd% %% + %%---------------------------------------------------------------------- -%% Purpose: Define common macros for testing +%% Purpose: Define common macros for (the snmp) testing %%---------------------------------------------------------------------- -%% - (some of the) Macros stolen from the test server - - -%% -define(line,put(test_server_loc,{?MODULE,?LINE}),). - - %% - Misc macros - -ifndef(APPLICATION). @@ -45,6 +41,8 @@ %% - Test case macros - +-define(TC_TRY(C, TC), snmp_test_lib:tc_try(C, TC)). +-define(TC_TRY(C, TCCond, TC), snmp_test_lib:tc_try(C, TCCond, TC)). -define(OS_BASED_SKIP(Skippable), snmp_test_lib:os_based_skip(Skippable)). -define(NON_PC_TC_MAYBE_SKIP(Config, Condition), diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml index cd28b95fd3..e6683dbd0b 100644 --- a/lib/ssh/doc/src/ssh_client_channel.xml +++ b/lib/ssh/doc/src/ssh_client_channel.xml @@ -150,12 +150,12 @@ <tag><c>{init_args(), list()}</c></tag> <item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item> - <tag><c>{cm, ssh:connection_ref()}</c></tag> + <tag><c>{cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>Reference to the <c>ssh</c> connection as returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso>. </p></item> - <tag><c>{channel_id, ssh:channel_id()}</c></tag> + <tag><c>{channel_id, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>Id of the <c>ssh</c> channel as returned by <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>. </p></item> @@ -198,7 +198,7 @@ {ok, ChannelRef} | {error, Reason}</name> <fsummary>Starts a process that handles an SSH channel.</fsummary> <type> - <v>SshConnection = ssh:connection_ref()</v> + <v>SshConnection = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> <d>As returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso></d> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> @@ -374,7 +374,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. It is sent just before the <seealso marker="#init-1">init/1</seealso> function @@ -393,21 +393,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_client_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 2a701929f6..9fa1da659c 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -40,128 +40,119 @@ <p>The <url href="http://www.ietf.org/rfc/rfc4254.txt">SSH Connection Protocol</url> is used by clients and servers, that is, SSH channels, to communicate over the SSH connection. The API functions in this module send SSH Connection Protocol events, - which are received as messages by the remote channel. - If the receiving channel is an Erlang process, the - messages have the format - <c><![CDATA[{ssh_cm, connection_ref(), ssh_event_msg()}]]></c>. + which are received as messages by the remote channel handling the remote channel. + The Erlang format of thoose messages is + (see also <seealso marker="#type-event">below</seealso>): + </p> + <p><c>{ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, </c><seealso marker="#type-channel_msg"><c>channel_msg()</c></seealso><c>}</c> + </p> + <p> If the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior is used to implement the channel process, these messages are handled by <seealso marker="ssh_client_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</seealso>.</p> </description> - <section> - <title>DATA TYPES</title> - - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data - type, or both:</p> - - <taglist> - <tag><c>boolean() =</c></tag> - <item><p><c>true | false </c></p></item> - <tag><c>string() =</c></tag> - <item><p>list of ASCII characters</p></item> - <tag><c>timeout() =</c></tag> - <item><p><c>infinity | integer()</c> in milliseconds</p></item> - <tag><c>connection_ref() =</c></tag> - <item><p>opaque() -as returned by - <c>ssh:connect/3</c> or sent to an SSH channel processes</p></item> - <tag><c>channel_id() =</c></tag> - <item><p><c>integer()</c></p></item> - <tag><c>ssh_data_type_code() =</c></tag> - <item><p><c>1</c> ("stderr") | <c>0</c> ("normal") are - valid values, see - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 5.2.</p></item> - <tag><c>ssh_request_status() =</c></tag> - <item><p> <c>success | failure</c></p></item> - <tag><c>event() =</c></tag> - <item><p><c>{ssh_cm, connection_ref(), ssh_event_msg()}</c></p></item> - <tag><c>ssh_event_msg() =</c></tag> - <item><p><c>data_events() | status_events() | terminal_events()</c></p></item> - <tag><c>reason() =</c></tag> - <item><p><c>timeout | closed</c></p></item> - </taglist> - - <taglist> - <tag><em>data_events()</em></tag> - <item> - <taglist> - <tag><c><![CDATA[{data, channel_id(), ssh_data_type_code(), Data :: binary()}]]></c></tag> - <item><p>Data has arrived on the channel. This event is sent as a - result of calling <seealso marker="ssh_connection#send-3"> - ssh_connection:send/[3,4,5]</seealso>.</p></item> - - <tag><c><![CDATA[{eof, channel_id()}]]></c></tag> - <item><p>Indicates that the other side sends no more data. - This event is sent as a result of calling <seealso - marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. - </p></item> - </taglist> - </item> + <datatypes> + <datatype> + <name name="ssh_data_type_code"/> + <desc> + <p>The valid values are <c>0</c> ("normal") and <c>1</c> ("stderr"), see + <url href="https://tools.ietf.org/html/rfc4254#page-8">RFC 4254, Section 5.2</url>.</p> + </desc> + </datatype> - <tag><em>status_events()</em></tag> - <item> + <datatype> + <name name="result"/> + <name name="reason"/> + <desc> + <p>The result of a call.</p> + <p>If the request reached the peer, was handled and the response + reached the requesting node the <seealso marker="#type-req_status">req_status()</seealso> + is the status reported from the peer.</p> + <p>If not, the <seealso marker="#type-reason">reason()</seealso> indicates what went wrong:</p> + <taglist> + <tag><c>closed</c></tag> + <item>indicates that the channel or connection was closed when trying to send the request + </item> + <tag><c>timeout</c></tag> + <item>indicates that the operation exceeded a time limit + </item> + </taglist> + </desc> + </datatype> - <taglist> - <tag><c><![CDATA[{signal, channel_id(), ssh_signal()}]]></c></tag> - <item><p>A signal can be delivered to the remote process/service - using the following message. Some systems do not support - signals, in which case they are to ignore this message. There is - currently no function to generate this event as the signals - referred to are on OS-level and not something generated by an - Erlang program.</p></item> + <datatype> + <name name="req_status"/> + <desc> + <p>The status of a request. + Coresponds to the <c>SSH_MSG_CHANNEL_SUCCESS</c> and <c>SSH_MSG_CHANNEL_FAILURE</c> values in + <url href="https://tools.ietf.org/html/rfc4254#section-5.4">RFC 4254, Section 5.4</url>. + </p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_signal, channel_id(), ExitSignal :: string(), ErrorMsg ::string(), - LanguageString :: string()}]]></c></tag> + <datatype_title>SSH Connection Protocol: General</datatype_title> + <datatype> + <name name="event"/> + <name name="channel_msg"/> + <desc> + <p>As mentioned in the introduction, the + <url href="https://tools.ietf.org/html/rfc4254">SSH Connection Protocol</url> + events are handled as messages. When writing a channel handling process without using + the support by the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> + behavior the process must handle thoose messages. + </p> + </desc> + </datatype> - <item><p>A remote execution can terminate violently because of a signal. - Then this message can be received. For details on valid string - values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> - Section 6.10, which shows a special case of these signals.</p></item> + <datatype> + <name name="want_reply"/> + <desc> + <p>Messages that include a <c>WantReply</c> expect the channel handling + process to call <seealso marker="ssh_connection#reply_request-4"> + ssh_connection:reply_request/4</seealso> + with the boolean value of <c>WantReply</c> as the second argument.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_status, channel_id(), ExitStatus :: integer()}]]></c></tag> - <item><p>When the command running at the other end terminates, the - following message can be sent to return the exit status of the - command. A zero <c>exit_status</c> usually means that the command - terminated successfully. This event is sent as a result of calling - <seealso marker="ssh_connection#exit_status-3"> - ssh_connection:exit_status/3</seealso>.</p></item> - <tag><c><![CDATA[{closed, channel_id()}]]></c></tag> - <item><p>This event is sent as a result of calling - <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. - Both the handling of this event and sending it are taken care of by the - <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p></item> - - </taglist> - </item> + <datatype_title>Data Transfer (RFC 4254, section 5.2)</datatype_title> + <datatype> + <name name="data_ch_msg"/> + <desc> + <p>Data has arrived on the channel. This event is sent as a result of calling + <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5]</seealso>. + </p> + </desc> + </datatype> - <tag><em>terminal_events()</em></tag> - <item> - <p>Channels implementing a shell and command execution on the - server side are to handle the following messages that can be sent by client- - channel processes.</p> + <datatype_title>Closing a Channel (RFC 4254, section 5.3)</datatype_title> + <datatype> + <name name="eof_ch_msg"/> + <desc> + <p>Indicates that the other side sends no more data. This event is sent as a result of calling + <seealso marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="closed_ch_msg"/> + <desc> + <p>This event is sent as a result of calling + <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. + Both the handling of this event and sending it are taken care of by the + <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p> + </desc> + </datatype> - <p>Events that include a <c>WantReply</c> expect the event handling - process to call <seealso marker="ssh_connection#reply_request-4"> - ssh_connection:reply_request/4</seealso> - with the boolean value of <c>WantReply</c> as the second argument.</p> - <taglist> - <tag><c><![CDATA[{env, channel_id(), WantReply :: boolean(), - Var ::string(), Value :: string()}]]></c></tag> - <item><p>Environment variables can be passed to the shell/command - to be started later. This event is sent as a result of calling <seealso - marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. - </p></item> - - <tag><c><![CDATA[{pty, channel_id(), - WantReply :: boolean(), {Terminal :: string(), CharWidth :: integer(), - RowHeight :: integer(), PixelWidth :: integer(), PixelHeight :: integer(), - TerminalModes :: [{Opcode :: atom() | integer(), - Value :: integer()}]}}]]></c></tag> - <item><p>A pseudo-terminal has been requested for the + <datatype_title>Requesting a Pseudo-Terminal (RFC 4254, section 6.2)</datatype_title> + <datatype> + <name name="pty_ch_msg"/> + <name name="term_mode"/> + <desc> + <p>A pseudo-terminal has been requested for the session. <c>Terminal</c> is the value of the TERM environment variable value, that is, <c>vt100</c>. Zero dimension parameters must be ignored. The character/row dimensions override the pixel @@ -169,46 +160,103 @@ drawable area of the window. <c>Opcode</c> in the <c>TerminalModes</c> list is the mnemonic name, represented as a lowercase Erlang atom, defined in - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>, Section 8. + <url href="https://tools.ietf.org/html/rfc4254#section-8">RFC 4254</url>, Section 8. It can also be an <c>Opcode</c> if the mnemonic name is not listed in the RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom: echo</c>. This event is sent as a result of calling <seealso - marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item> + marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p> + </desc> + </datatype> + - <tag><c><![CDATA[{shell, WantReply :: boolean()}]]></c></tag> - <item><p>This message requests that the user default shell + <datatype_title>Environment Variable Passing (RFC 4254, section 6.4)</datatype_title> + <datatype> + <name name="env_ch_msg"/> + <desc> + <p>Environment variables can be passed to the shell/command + to be started later. This event is sent as a result of calling <seealso + marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. + </p> + </desc> + </datatype> + + + <datatype_title>Starting a Shell or Command (RFC 4254, section 6.5)</datatype_title> + <datatype> + <name name="shell_ch_msg"/> + <desc> + <p>This message requests that the user default shell is started at the other end. This event is sent as a result of calling <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>. - </p></item> + </p> + </desc> + </datatype> + <datatype> + <name name="exec_ch_msg"/> + <desc> + <p>This message requests that the server starts + execution of the given command. This event is sent as a result of calling <seealso + marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. + </p> + </desc> + </datatype> + - <tag><c><![CDATA[{window_change, channel_id(), CharWidth() :: integer(), - RowHeight :: integer(), PixWidth :: integer(), PixHeight :: integer()}]]></c></tag> - <item><p>When the window (terminal) size changes on the client + <datatype_title>Window Dimension Change Message (RFC 4254, section 6.7)</datatype_title> + <datatype> + <name name="window_change_ch_msg"/> + <desc> + <p>When the window (terminal) size changes on the client side, it <em>can</em> send a message to the server side to inform it of - the new dimensions. No API function generates this event.</p></item> + the new dimensions. No API function generates this event.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exec, channel_id(), - WantReply :: boolean(), Cmd :: string()}]]></c></tag> - <item><p>This message requests that the server starts - execution of the given command. This event is sent as a result of calling <seealso - marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. - </p></item> - </taglist> - </item> - </taglist> - </section> + <datatype_title>Signals (RFC 4254, section 6.9)</datatype_title> + <datatype> + <name name="signal_ch_msg"/> + <desc> + <p>A signal can be delivered to the remote process/service + using the following message. Some systems do not support + signals, in which case they are to ignore this message. There is + currently no function to generate this event as the signals + referred to are on OS-level and not something generated by an + Erlang program.</p> + </desc> + </datatype> + + + <datatype_title>Returning Exit Status (RFC 4254, section 6.10)</datatype_title> + <datatype> + <name name="exit_status_ch_msg"/> + <desc> + <p>When the command running at the other end terminates, the + following message can be sent to return the exit status of the + command. A zero <c>exit_status</c> usually means that the command + terminated successfully. This event is sent as a result of calling + <seealso marker="ssh_connection#exit_status-3"> + ssh_connection:exit_status/3</seealso>.</p> + </desc> + </datatype> + <datatype> + <name name="exit_signal_ch_msg"/> + <desc> + <p>A remote execution can terminate violently because of a signal. + Then this message can be received. For details on valid string + values, see <url href="https://tools.ietf.org/html/rfc4254#section-6.10">RFC 4254</url> + Section 6.10, which shows a special case of these signals.</p> + </desc> + </datatype> + + </datatypes> + <funcs> <func> - <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> + <name since="" name="adjust_window" arity="3"/> <fsummary>Adjusts the SSH flow control window.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>NumOfBytes = integer()</v> - </type> - <desc> + <desc> <p>Adjusts the SSH flow control window. This is to be done by both the client- and server-side channel processes.</p> @@ -221,17 +269,12 @@ </func> <func> - <name since="">close(ConnectionRef, ChannelId) -> ok</name> + <name since="" name="close" arity="2"/> <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>A server- or client-channel process can choose to close their session by sending a close event. </p> - <note><p>This function is called by the <c>ssh_client_channel</c> behavior when the channel is terminated, see <seealso marker="ssh_client_channel"> ssh_client_channel(3)</seealso>. Thus, channels implemented @@ -240,57 +283,61 @@ </func> <func> - <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="exec" arity="4"/> <fsummary>Requests that the server starts the execution of the given command.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Command = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process to request that the server starts executing the given command. The result is several messages according to the following pattern. The last message is a channel close message, as the <c>exec</c> request is a one-time execution that closes the channel when it is done.</p> - <taglist> - <tag><c>N x {ssh_cm, connection_ref(), - {data, channel_id(), ssh_data_type_code(), Data :: binary()}}</c></tag> + <!--taglist> + <tag><c>N x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {data, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso><c>, Data :: binary()}}</c></tag> <item><p>The result of executing the command can be only one line or thousands of lines depending on the command.</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {eof, channel_id()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {eof, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that no more data is to be sent.</p></item> - <tag><c>0 or 1 x {ssh_cm, - connection_ref(), {exit_signal, - channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_signal, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> <item><p>Not all systems send signals. For details on valid string values, see RFC 4254, Section 6.10</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {exit_status, - channel_id(), ExitStatus :: integer()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_status, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitStatus :: integer()}}</c></tag> <item><p>It is recommended by the SSH Connection Protocol to send this message, but that is not always the case.</p></item> - <tag><c>1 x {ssh_cm, connection_ref(), - {closed, channel_id()}}</c></tag> + <tag><c>1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that the <c>ssh_client_channel</c> started for the execution of the command has now been shut down.</p></item> + </taglist--> + + <taglist> + <tag>N x <seealso marker="#type-data_ch_msg">data message(s)</seealso></tag> + <item><p>The result of executing the command can be only one line + or thousands of lines depending on the command.</p></item> + + <tag>0 or 1 x <seealso marker="#type-eof_ch_msg">eof message</seealso></tag> + <item><p>Indicates that no more data is to be sent.</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_signal_ch_msg">exit signal message</seealso></tag> + <item><p>Not all systems send signals. For details on valid string + values, see RFC 4254, Section 6.10</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_status_ch_msg">exit status message</seealso></tag> + <item><p>It is recommended by the SSH Connection Protocol to send this + message, but that is not always the case.</p></item> + + <tag>1 x <seealso marker="#type-closed_ch_msg">closed status message</seealso></tag> + <item><p>Indicates that the <c>ssh_client_channel</c> started for the + execution of the command has now been shut down.</p></item> </taglist> </desc> </func> <func> - <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name> + <name since="" name="exit_status" arity="3"/> <fsummary>Sends the exit status of a command to the client.</fsummary> - <type> - <v>ConnectionRef = connection_ref() </v> - <v>ChannelId = channel_id()</v> - <v>Status = integer()</v> - </type> <desc> <p>Is to be called by a server-channel process to send the exit status of a command to the client.</p> @@ -298,16 +345,10 @@ </func> <func> - <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name> - <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | - {error, reason()}</name> + <name since="OTP 17.5" name="ptty_alloc" arity="3"/> + <name since="OTP 17.4" name="ptty_alloc" arity="4"/> <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Options = proplists:proplist()</v> - </type> <desc> <p>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal. Is to be called by an SSH client process.</p> @@ -339,14 +380,8 @@ </func> <func> - <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> + <name since="" name="reply_request" arity="4"/> <fsummary>Sends status replies to requests that want such replies.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>WantReply = boolean()</v> - <v>Status = ssh_request_status()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends status replies to requests where the requester has stated that it wants a status report, that is, <c>WantReply = true</c>. @@ -361,14 +396,15 @@ <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> - ok | {error, timeout} | {error, closed}</name> + ok | Error</name> <fsummary>Sends channel data.</fsummary> <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>ChannelId = <seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>Data = binary()</v> - <v>Type = ssh_data_type_code()</v> + <v>Type = <seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso></v> <v>Timeout = timeout()</v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>Is to be called by client- and server-channel processes to send data to each other. @@ -380,29 +416,17 @@ </func> <func> - <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> + <name since="" name="send_eof" arity="2"/> <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends EOF on channel <c>ChannelId</c>.</p> </desc> </func> <func> - <name since="">session_channel(ConnectionRef, Timeout) -></name> - <name since="">session_channel(ConnectionRef, InitialWindowSize, - MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name> + <name since="" name="session_channel" arity="2"/> + <name since="" name="session_channel" arity="4"/> <fsummary>Opens a channel for an SSH session.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>InitialWindowSize = integer()</v> - <v>MaxPacketSize = integer()</v> - <v>Timeout = timeout()</v> - <v>Reason = term()</v> - </type> <desc> <p>Opens a channel for an SSH session. The channel id returned from this function is the id used as input to the other functions in this module.</p> @@ -410,17 +434,9 @@ </func> <func> - <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="setenv" arity="5"/> <fsummary>Environment variables can be passed to the shell/command to be started later.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Var = string()</v> - <v>Value = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Environment variables can be passed before starting the shell/command. Is to be called by a client channel processes.</p> @@ -428,14 +444,9 @@ </func> <func> - <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed} - </name> + <name since="" name="shell" arity="2"/> <fsummary>Requests that the user default shell (typically defined in /etc/passwd in Unix systems) is to be executed at the server end.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Is to be called by a client channel process to request that the user default shell (typically defined in /etc/passwd in Unix systems) is executed @@ -448,15 +459,8 @@ </func> <func> - <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="subsystem" arity="4"/> <fsummary>Requests to execute a predefined subsystem on the server.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Subsystem = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process for requesting to execute a predefined subsystem on the server. diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml index a4e18bbfbf..87c745c9fb 100644 --- a/lib/ssh/doc/src/ssh_server_channel.xml +++ b/lib/ssh/doc/src/ssh_server_channel.xml @@ -112,7 +112,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. This is especially useful if the server wants to send a message to the client without first @@ -129,21 +129,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_server_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index c89092798d..f9f1e0953b 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -37,60 +37,112 @@ SSH.</p> </description> - <section> - <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data type, or both: - </p> - - <taglist> - <tag><c>reason()</c></tag> - <item> - <p>= <c>atom() | string() | tuple() </c>A description of the reason why an operation failed.</p> - <p> - The <c>atom()</c> value is formed from the sftp error codes in the protocol-level responses as defined in - <url href="https://tools.ietf.org/id/draft-ietf-secsh-filexfer-13.txt">draft-ietf-secsh-filexfer-13.txt</url> - section 9.1. - </p> - <p> + <datatypes> + <datatype> + <name name="sftp_option"/> + <desc> + </desc> + </datatype> + + <datatype_title>Error cause</datatype_title> + <datatype> + <name name="reason"/> + <desc> + <p>A description of the reason why an operation failed.</p> + <p>The <c>atom()</c> value is formed from the sftp error codes in the protocol-level responses as defined in + <url href="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-49">draft-ietf-secsh-filexfer-13</url> + section 9.1. The codes are named as <c>SSH_FX_*</c> which are transformed into lowercase of the star-part. E.g. the error code <c>SSH_FX_NO_SUCH_FILE</c> will cause the <c>reason()</c> to be <c>no_such_file</c>. </p> <p>The <c>string()</c> reason is the error information from the server in case of an exit-signal. If that information is empty, the reason is the exit signal name. </p> - <p>The <c>tuple()</c> reason are other errors like the <c>{exit_status,integer()}</c> if the exit status is not 0. + <p>The <c>tuple()</c> reason are other errors like for example <c>{exit_status,1}</c>. </p> - </item> + </desc> + </datatype> - <tag><c>connection_ref() =</c></tag> - <item><p><c>opaque()</c> - as returned by - <seealso marker="ssh#connect-3"><c>ssh:connect/3</c></seealso></p></item> + <datatype_title>Crypto operations for open_tar</datatype_title> + <datatype> + <name name="tar_crypto_spec"/> + <name name="encrypt_spec"/> + <name name="decrypt_spec"/> + <desc> + <p>Specifies the encryption or decryption applied to tar files when using + <seealso marker="#open_tar/3">open_tar/3</seealso> or + <seealso marker="#open_tar/4">open_tar/4</seealso>. + </p> + <p>The encryption or decryption is applied to the generated stream of + bytes prior to sending the resulting stream to the SFTP server. + </p> + <p>For code examples see Section + <seealso marker="using_ssh#example-with-encryption">Example with encryption</seealso> + in the ssh Users Guide. + </p> + </desc> + </datatype> - <tag><c>timeout()</c></tag> - <item><p>= <c>infinity | integer()</c> in milliseconds. Default infinity.</p></item> - </taglist> - </section> + <datatype> + <name name="init_fun"/> + <name name="chunk_size"/> + <name name="crypto_state"/> + <desc> + <p>The <c>init_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied once prior to any other <c>crypto</c> + operation. The intention is that this function initiates the encryption or + decryption for example by calling + <seealso marker="crypto:crypto#crypto_init/4">crypto:crypto_init/4</seealso> + or similar. The <c>crypto_state()</c> is the state such a function may return. + </p> + <p>If the selected cipher needs to have the input data partioned into + blocks of a certain size, the <c>init_fun()</c> should return the second + form of return value with the <c>chunk_size()</c> set to the block size. + If the <c>chunk_size()</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, + because this is intended for stream crypto, whereas a fixed <c>chunk_size()</c> is intended for block crypto. + A <c>chunk_size()</c> can be changed in the return from the <c>crypto_fun()</c>. + The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + </p> + </desc> + </datatype> - <section> - <title>Time-outs</title> - <p>If the request functions for the SFTP channel return <c>{error, timeout}</c>, - no answer was received from the server within the expected time.</p> - <p>The request may have reached the server and may have been performed. - However, no answer was received from the server within the expected time.</p> - </section> + <datatype> + <name name="crypto_fun"/> + <name name="crypto_result"/> + <desc> + <p>The initial <c>crypto_state()</c> returned from the + <seealso marker="#type-init_fun">init_fun()</seealso> + is folded into repeated applications of the <c>crypto_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>. + The binary returned from that fun is sent to the remote SFTP server and + the new <c>crypto_state()</c> is used in the next call of the + <c>crypto_fun()</c>. + </p> + <p>If the <c>crypto_fun()</c> reurns a <c>chunk_size()</c>, that value + is as block size for further blocks in calls to <c>crypto_fun()</c>. + </p> + </desc> + </datatype> + + <datatype> + <name name="final_fun"/> + <desc> + <p>If doing encryption, + the <c>final_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied to the last piece of data. + The <c>final_fun()</c> is responsible for padding (if needed) and + encryption of that last piece. + </p> + </desc> + </datatype> + </datatypes> <funcs> <func> - <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name> + <name name="apread" arity="4" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apread/4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#aread-3"><c>aread/3</c></seealso> functions.</p> @@ -98,17 +150,8 @@ </func> <func> - <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name> + <name name="apwrite" arity="4" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apwrite/4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#awrite-3"><c>awrite/3</c></seealso> functions.</p> @@ -116,15 +159,8 @@ </func> <func> - <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name> + <name name="aread" arity="3" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc> <p>Reads from an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -137,16 +173,8 @@ </func> <func> - <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name> + <name name="awrite" arity="3" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes to an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -159,28 +187,18 @@ </func> <func> - <name since="">close(ChannelPid, Handle) -></name> - <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name> + <name name="close" arity="2" since=""/> + <name name="close" arity="3" since=""/> <fsummary>Closes an open handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Closes a handle to an open file or directory on the server.</p> </desc> </func> <func> - <name since="">delete(ChannelPid, Name) -></name> - <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="delete" arity="2" since=""/> + <name name="delete" arity="3" since=""/> <fsummary>Deletes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes the file specified by <c><![CDATA[Name]]></c>. </p> @@ -188,14 +206,9 @@ </func> <func> - <name since="">del_dir(ChannelPid, Name) -></name> - <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="del_dir" arity="2" since=""/> + <name name="del_dir" arity="3" since=""/> <fsummary>Deletes an empty directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. The directory must be empty before it can be successfully deleted. @@ -204,16 +217,9 @@ </func> <func> - <name since="">list_dir(ChannelPid, Path) -></name> - <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name> + <name name="list_dir" arity="2" since=""/> + <name name="list_dir" arity="3" since=""/> <fsummary>Lists the directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Filenames = [Filename]</v> - <v>Filename = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Lists the given directory on the server, returning the filenames as a list of strings.</p> @@ -221,14 +227,9 @@ </func> <func> - <name since="">make_dir(ChannelPid, Name) -></name> - <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="make_dir" arity="2" since=""/> + <name name="make_dir" arity="3" since=""/> <fsummary>Creates a directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> must be a full path to a new directory. The directory can only be @@ -237,14 +238,9 @@ </func> <func> - <name since="">make_symlink(ChannelPid, Name, Target) -></name> - <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name> + <name name="make_symlink" arity="3" since=""/> + <name name="make_symlink" arity="4" since=""/> <fsummary>Creates a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the name <c><![CDATA[Name]]></c>. @@ -252,32 +248,19 @@ </desc> </func> - <func> - <name since="">open(ChannelPid, File, Mode) -></name> - <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <func> + <name name="open" arity="3" since=""/> + <name name="open" arity="4" since=""/> <fsummary>Opens a file and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Mode = [Modeflag]</v> - <v>Modeflag = read | write | creat | trunc | append | binary</v> - <v>Timeout = timeout()</v> - <v>Handle = term()</v> - </type> <desc> <p>Opens a file on the server and returns a handle, which can be used for reading or writing.</p> </desc> </func> <func> - <name since="">opendir(ChannelPid, Path) -></name> - <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="opendir" arity="2" since=""/> + <name name="opendir" arity="3" since=""/> <fsummary>Opens a directory and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a directory on the server. The handle can be used for reading directory contents.</p> @@ -285,72 +268,36 @@ </func> <func> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="open_tar" arity="3" since="OTP 17.4"/> + <name name="open_tar" arity="4" since="OTP 17.4"/> <fsummary>Opens a tar file on the server to which <c>ChannelPid</c> is connected and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt]</v> - <v>EncryptOpt = {crypto,{InitFun,EncryptFun,CloseFun}}</v> - <v>DecryptOpt = {crypto,{InitFun,DecryptFun}}</v> - <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize})</v> - <v>CryptoState = any()</v> - <v>ChunkSize = undefined | pos_integer()</v> - <v>EncryptFun = (fun(PlainBin,CryptoState) -> EncryptResult)</v> - <v>EncryptResult = {ok,EncryptedBin,CryptoState} | {ok,EncryptedBin,CryptoState,ChunkSize}</v> - <v>PlainBin = binary()</v> - <v>EncryptedBin = binary()</v> - <v>DecryptFun = (fun(EncryptedBin,CryptoState) -> DecryptResult)</v> - <v>DecryptResult = {ok,PlainBin,CryptoState} | {ok,PlainBin,CryptoState,ChunkSize}</v> - <v>CloseFun = (fun(PlainBin,CryptoState) -> {ok,EncryptedBin})</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>. - The handle can be used for remote tar creation and extraction, as defined by the - <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function. - </p> - - <p> For code exampel see Section - <seealso marker="using_ssh">SFTP Client with TAR Compression and Encryption</seealso> in - the ssh Users Guide. </p> - - <p>The <c>crypto</c> mode option is applied to the generated stream of bytes prior to sending - them to the SFTP server. This is intended for encryption but can be used for other - purposes. + The handle can be used for remote tar creation and extraction. The actual writing + and reading is performed by calls to + <seealso marker="stdlib:erl_tar#add-3">erl_tar:add/3,4</seealso> and + <seealso marker="stdlib:erl_tar#extract-2">erl_tar:extract/2</seealso>. + Note: The + <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function should not + be called, that one is called by this open_tar function. </p> - <p>The <c>InitFun</c> is applied once - prior to any other <c>crypto</c> operation. The returned <c>CryptoState</c> is then folded into - repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned - from those funs are sent further to the remote SFTP server. Finally, if doing encryption, - the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is - responsible for padding (if needed) and encryption of that last piece. + <p>For code examples see Section + <seealso marker="using_ssh#sftp-client-with-tar-compression">SFTP Client with TAR Compression</seealso> + in the ssh Users Guide. </p> - <p>The <c>ChunkSize</c> defines the size of the <c>PlainBin</c>s that <c>EncodeFun</c> is applied - to. If the <c>ChunkSize</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, - because this is intended for stream crypto, whereas a fixed <c>ChunkSize</c> is intended for block crypto. - <c>ChunkSize</c>s can be changed in the return from the <c>EncryptFun</c> or - <c>DecryptFun</c>. The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + <p>The <c>crypto</c> mode option is explained in the data types section above, see + <seealso marker="#Crypto operations for open_tar">Crypto operations for open_tar</seealso>. + Encryption is assumed if the <c>Mode</c> contains <c>write</c>, and + decryption if the <c>Mode</c> contains <c>read</c>. </p> - </desc> </func> <func> - <name since="">position(ChannelPid, Handle, Location) -></name> - <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name> + <name name="position" arity="3" since=""/> + <name name="position" arity="4" since=""/> <fsummary>Sets the file position of a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Location = Offset - | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v> - <v>Offset = integer()</v> - <v>Timeout = timeout()</v> - <v>NewPosition = integer()</v> - </type> <desc> <p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if @@ -384,17 +331,9 @@ </func> <func> - <name since="">pread(ChannelPid, Handle, Position, Len) -></name> - <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="pread" arity="4" since=""/> + <name name="pread" arity="5" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc><p>The <c><![CDATA[pread/3,4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#read-3"><c>read/3,4</c></seealso> functions.</p> @@ -402,16 +341,9 @@ </func> <func> - <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name> - <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name> + <name name="pwrite" arity="4" since=""/> + <name name="pwrite" arity="5" since=""/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc><p>The <c><![CDATA[pwrite/3,4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#write-3"><c>write/3,4</c></seealso> functions.</p> @@ -419,16 +351,9 @@ </func> <func> - <name since="">read(ChannelPid, Handle, Len) -></name> - <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="read" arity="3" since=""/> + <name name="read" arity="4" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc> <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or @@ -440,32 +365,19 @@ </desc> </func> - <func> - <name since="">read_file(ChannelPid, File) -></name> - <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name> + <func> + <name name="read_file" arity="2" since=""/> + <name name="read_file" arity="3" since=""/> <fsummary>Reads a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Reads a file from the server, and returns the data in a binary.</p> </desc> </func> - <func> - <name since="">read_file_info(ChannelPid, Name) -></name> - <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name name="read_file_info" arity="2" since=""/> + <name name="read_file_info" arity="3" since=""/> <fsummary>Gets information about a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the file system object specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. See @@ -474,38 +386,26 @@ </p> <p> Depending on the underlying OS:es links might be followed and info on the final file, directory - etc is returned. See <seealso marker="#read_link_info-2">ssh_sftp::read_link_info/2</seealso> + etc is returned. See <seealso marker="#read_link_info-2">read_link_info/2</seealso> on how to get information on links instead. </p> </desc> </func> <func> - <name since="">read_link(ChannelPid, Name) -></name> - <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name> + <name name="read_link" arity="2" since=""/> + <name name="read_link" arity="3" since=""/> <fsummary>Reads symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Reads the link target from the symbolic link specified by <c><![CDATA[name]]></c>. </p> </desc> </func> - <func> - <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name> - <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name since="" name="read_link_info" arity="2"/> + <name since="" name="read_link_info" arity="3"/> <fsummary>Gets information about a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. @@ -517,15 +417,9 @@ </func> <func> - <name since="">rename(ChannelPid, OldName, NewName) -> </name> - <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name> + <name since="" name="rename" arity="3"/> + <name since="" name="rename" arity="4"/> <fsummary>Renames a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>OldName = string()</v> - <v>NewName = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Renames a file named <c><![CDATA[OldName]]></c> and gives it the name <c><![CDATA[NewName]]></c>. @@ -535,25 +429,27 @@ <func> <name since="">start_channel(ConnectionRef) -></name> - <name since="">start_channel(ConnectionRef, Options) -> - {ok, Pid} | {error, reason()|term()}</name> + <name since="">start_channel(ConnectionRef, SftpOptions) -> + {ok, ChannelPid} | Error</name> + <name since="">start_channel(Host) -></name> <name since="">start_channel(Host, Options) -></name> - <name since="">start_channel(Host, Port, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> - + <name since="">start_channel(Host, Port, Options) -></name> <name since="">start_channel(TcpSocket) -></name> <name since="">start_channel(TcpSocket, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> + {ok, ChannelPid, ConnectionRef} | Error</name> <fsummary>Starts an SFTP client.</fsummary> <type> - <v>Host = string()</v> - <v>ConnectionRef = connection_ref()</v> - <v>Port = integer()</v> - <v>TcpSocket = port()</v> - <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> - <v>Options = [{Option, Value}]</v> + <v>Host = <seealso marker="ssh:ssh#type-host">ssh:host()</seealso></v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> + <v>TcpSocket = <seealso marker="ssh:ssh#type-open_socket">ssh:open_socket()</seealso></v> + <v>Options = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> + | <seealso marker="ssh:ssh#type-client_option">ssh:client_option()</seealso> ]</v> + <v>SftpOptions = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> ]</v> + <v>ChannelPid = pid()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>If no connection reference is provided, a connection is set @@ -594,11 +490,8 @@ </func> <func> - <name since="">stop_channel(ChannelPid) -> ok</name> + <name since="" name="stop_channel" arity="1"/> <fsummary>Stops the SFTP client channel.</fsummary> - <type> - <v>ChannelPid = pid()</v> - </type> <desc> <p>Stops an SFTP channel. Does not close the SSH connection. Use <seealso marker="ssh#close-1">ssh:close/1</seealso> to close it.</p> @@ -606,16 +499,9 @@ </func> <func> - <name since="">write(ChannelPid, Handle, Data) -></name> - <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write" arity="3"/> + <name since="" name="write" arity="4"/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>. The file is to be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c> @@ -625,15 +511,9 @@ </func> <func> - <name since="">write_file(ChannelPid, File, Iolist) -></name> - <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file" arity="3"/> + <name since="" name="write_file" arity="4"/> <fsummary>Writes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Iolist = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes a file to the server. The file is created if it does not exist but overwritten if it exists.</p> @@ -641,15 +521,9 @@ </func> <func> - <name since="">write_file_info(ChannelPid, Name, Info) -></name> - <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file_info" arity="3"/> + <name since="" name="write_file_info" arity="4"/> <fsummary>Writes information for a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Info = record()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the file specified by <c><![CDATA[Name]]></c>. See diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml index ee72784add..0d7b340399 100644 --- a/lib/ssh/doc/src/ssh_sftpd.xml +++ b/lib/ssh/doc/src/ssh_sftpd.xml @@ -35,36 +35,23 @@ <p>Specifies a channel process to handle an SFTP subsystem.</p> </description> - <section> - <title>DATA TYPES</title> - <taglist> - <tag><c>subsystem_spec() =</c></tag> - <item><p><c>{subsystem_name(), {channel_callback(), channel_init_args()}}</c></p></item> - <tag><c>subsystem_name() =</c></tag> - <item><p><c>"sftp"</c></p></item> - <tag><c>channel_callback() =</c></tag> - <item><p><c>atom()</c> - Name of the Erlang module implementing the subsystem using the - <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour.</p></item> - <tag><c>channel_init_args() =</c></tag> - <item><p><c>list()</c> - The one given as argument to function <c>subsystem_spec/1</c>.</p></item> - </taglist> - </section> <funcs> <func> - <name since="">subsystem_spec(Options) -> subsystem_spec()</name> + <name name="subsystem_spec" arity="1" since=""/> <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary> - <type> - <v>Options = [{Option, Value}]</v> - </type> <desc> <p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p> + <p>The <c>Name</c> is <c>"sftp"</c> and + <c>CbMod</c> is the name of the Erlang module implementing the subsystem using the + <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour. + </p> <p>Options:</p> <taglist> - <tag><c><![CDATA[{cwd, String}]]></c></tag> + <tag><c>cwd</c></tag> <item> <p>Sets the initial current working directory for the server.</p> </item> - <tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag> + <tag><c>file_handler</c></tag> <item> <p>Determines which module to call for accessing the file server. The default value is <c>ssh_sftpd_file</c>, which uses the @@ -72,13 +59,13 @@ APIs to access the standard OTP file server. This option can be used to plug in other file servers.</p> </item> - <tag><c><![CDATA[{max_files, Integer}]]></c></tag> + <tag><c>max_files</c></tag> <item> <p>The default value is <c>0</c>, which means that there is no upper limit. If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c> request is limited to at most the given value.</p> </item> - <tag><c><![CDATA[{root, String}]]></c></tag> + <tag><c>root</c></tag> <item> <p>Sets the SFTP root directory. Then the user cannot see any files above this root. If, for example, the root directory is set to <c>/tmp</c>, @@ -86,7 +73,7 @@ <c>cd /etc</c>, the user moves to <c>/tmp/etc</c>. </p> </item> - <tag><c><![CDATA[{sftpd_vsn, integer()}]]></c></tag> + <tag><c>sftpd_vsn</c></tag> <item> <p>Sets the SFTP version to use. Defaults to 5. Version 6 is under development and limited.</p> diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml index 4455d5ecc5..5c56dee81d 100644 --- a/lib/ssh/doc/src/using_ssh.xml +++ b/lib/ssh/doc/src/using_ssh.xml @@ -232,9 +232,10 @@ </section> <section> - <title>SFTP Client with TAR Compression and Encryption</title> - - <p>Example of writing and then reading a tar file follows:</p> + <title>SFTP Client with TAR Compression</title> + <section> + <title>Basic example</title> + <p>This is an example of writing and then reading a tar file:</p> <code type="erl"> {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]), ok = erl_tar:add(HandleWrite, .... ), @@ -248,8 +249,12 @@ {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), ok = erl_tar:close(HandleRead), </code> + </section> - <p>The previous write and read example can be extended with encryption and decryption as follows:</p> + <section> + <title>Example with encryption</title> + <p>The previous <seealso marker="using_ssh#basic-example">Basic example</seealso> + can be extended with encryption and decryption as follows:</p> <code type="erl"> %% First three parameters depending on which crypto type we select: Key = <<"This is a 256 bit key. abcdefghi">>, @@ -297,6 +302,7 @@ Cr = {InitFun,DecryptFun}, ok = erl_tar:close(HandleRead), </code> </section> + </section> <section> <marker id="usersguide_creating_a_subsystem"/> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff5aee14d7..32f10c797d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -66,6 +66,8 @@ cipher_alg/0, mac_alg/0, compression_alg/0, + host/0, + open_socket/0, ip_port/0 ]). diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index f985d8e273..3bd1e1fdf1 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -52,7 +52,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 9a060b8304..d6b50613f9 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -263,11 +263,8 @@ -record(connection, { requests = [], %% [{ChannelId, Pid}...] awaiting reply on request, channel_cache, - port_bindings, channel_id_seed, cli_spec, - address, - port, options, exec, system_supervisor, diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 83f85b1d8e..c5316bf133 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -60,13 +60,121 @@ request_failure_msg/0, request_success_msg/1, - bind/4, unbind/3, unbind_channel/2, - bound_channel/3, encode_ip/1 + encode_ip/1 ]). -type connection_ref() :: ssh:connection_ref(). -type channel_id() :: ssh:channel_id(). +-type req_status() :: success | failure . +-type reason() :: closed | timeout . + +-type result() :: req_status() | {error, reason()} . + +-type ssh_data_type_code() :: non_neg_integer(). % Only 0 and 1 are used + + +%%% The SSH Connection Protocol + +-export_type([event/0, + channel_msg/0, + want_reply/0, + data_ch_msg/0, + eof_ch_msg/0, + signal_ch_msg/0, + exit_signal_ch_msg/0, + exit_status_ch_msg/0, + closed_ch_msg/0, + env_ch_msg/0, + pty_ch_msg/0, + shell_ch_msg/0, + window_change_ch_msg/0, + exec_ch_msg/0 + ]). + +-type event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}. +-type channel_msg() :: data_ch_msg() + | eof_ch_msg() + | closed_ch_msg() + | pty_ch_msg() + | env_ch_msg() + | shell_ch_msg() + | exec_ch_msg() + | signal_ch_msg() + | window_change_ch_msg() + | exit_status_ch_msg() + | exit_signal_ch_msg() + . + +-type want_reply() :: boolean(). + +-type data_ch_msg() :: {data, + ssh:channel_id(), + ssh_data_type_code(), + Data :: binary() + } . +-type eof_ch_msg() :: {eof, + ssh:channel_id() + } . +-type signal_ch_msg() :: {signal, + ssh:channel_id(), + SignalName :: string() + } . +-type exit_signal_ch_msg() :: {exit_signal, ssh:channel_id(), + ExitSignal :: string(), + ErrorMsg :: string(), + LanguageString :: string()} . +-type exit_status_ch_msg() :: {exit_status, + ssh:channel_id(), + ExitStatus :: non_neg_integer() + } . +-type closed_ch_msg() :: {closed, + ssh:channel_id() + } . +-type env_ch_msg() :: {env, + ssh:channel_id(), + want_reply(), + Var :: string(), + Value :: string() + } . +-type pty_ch_msg() :: {pty, + ssh:channel_id(), + want_reply(), + {Terminal :: string(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer(), + TerminalModes :: [term_mode()] + } + } . + +-type term_mode() :: {Opcode :: atom() | byte(), + Value :: non_neg_integer()} . + +-type shell_ch_msg() :: {shell, + ssh:channel_id(), + want_reply() + } . +-type window_change_ch_msg() :: {window_change, + ssh:channel_id(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer() + } . +-type exec_ch_msg() :: {exec, + ssh:channel_id(), + want_reply(), + Command :: string() + } . + +%%% This function is soley to convince all +%%% checks that the type event() exists... +-export([dummy/1]). +-spec dummy(event()) -> false. +dummy(_) -> false. + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- @@ -77,14 +185,21 @@ %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(ConnectionRef, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, Timeout) -> session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. + +-spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + InitialWindowSize :: pos_integer(), + MaxPacketSize :: pos_integer(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, @@ -100,8 +215,11 @@ session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec exec(ConnectionRef, ChannelId, Command, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Command :: string(), + Timeout :: timeout(). exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", @@ -112,8 +230,10 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> - ok | success | failure | {error, timeout}. +-spec shell(ConnectionRef, ChannelId) -> Result when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Result :: ok | success | failure | {error, timeout} . shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, @@ -122,8 +242,11 @@ shell(ConnectionHandler, ChannelId) -> %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Subsystem :: string(), + Timeout :: timeout(). subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), @@ -134,12 +257,13 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, timeout | closed}. + send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; + (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); @@ -151,14 +275,15 @@ send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. +-spec send_eof(ConnectionRef, ChannelId) -> ok | {error, closed} when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends eof on the channel <ChannelId>. @@ -167,7 +292,10 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. +-spec adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + NumOfBytes :: integer(). %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -176,8 +304,12 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Var :: string(), + Value :: string(), + Timeout :: timeout(). %% %% %% Description: Environment variables may be passed to the shell/command to be @@ -189,7 +321,9 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(connection_ref(), channel_id()) -> ok. +-spec close(ConnectionRef, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends a close message on the channel <ChannelId>. @@ -198,7 +332,11 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + WantReply :: boolean(), + Status :: req_status(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Send status replies to requests that want such replies. @@ -211,15 +349,20 @@ reply_request(_,false, _, _) -> %%-------------------------------------------------------------------- %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failure | {error, timeout}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(). ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(), + Timeout :: timeout(). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME @@ -252,6 +395,10 @@ signal(ConnectionHandler, Channel, Sig) -> "signal", false, [?string(Sig)], 0). +-spec exit_status(ConnectionRef, ChannelId, Status) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Status :: integer(). exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). @@ -713,29 +860,6 @@ request_success_msg(Data) -> %%%---------------------------------------------------------------- %%% %%% -bind(IP, Port, ChannelPid, Connection) -> - Binds = [{{IP, Port}, ChannelPid} - | lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)], - Connection#connection{port_bindings = Binds}. - -unbind(IP, Port, Connection) -> - Connection#connection{ - port_bindings = - lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)}. -unbind_channel(ChannelPid, Connection) -> - Binds = [{Bind, ChannelP} || {Bind, ChannelP} - <- Connection#connection.port_bindings, - ChannelP =/= ChannelPid], - Connection#connection{port_bindings = Binds}. - -bound_channel(IP, Port, Connection) -> - case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of - {value, {{IP, Port}, ChannelPid}} -> ChannelPid; - _ -> undefined - end. - encode_ip(Addr) when is_tuple(Addr) -> case catch inet_parse:ntoa(Addr) of {'EXIT',_} -> false; diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8f32966a12..e984cbb21b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -386,16 +386,24 @@ init_connection_handler(Role, Socket, Opts) -> D); {stop, Error} -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - C = #connection{system_supervisor = proplists:get_value(system_sup, Sups), - sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), - connection_supervisor = proplists:get_value(connection_sup, Sups) - }, + D = try + %% Only servers have supervisorts defined in Opts + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + #connection{system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + } + of + C -> + #data{connection_state=C} + catch + _:_ -> + #data{connection_state=#connection{}} + end, gen_statem:enter_loop(?MODULE, [], {init_error,Error}, - #data{connection_state=C, - socket=Socket}) + D#data{socket=Socket}) end. @@ -406,7 +414,6 @@ init([Role,Socket,Opts]) -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, - port_bindings = [], requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), @@ -1550,7 +1557,7 @@ terminate({shutdown,"Connection closed"}, _StateName, D) -> terminate({shutdown,{init,Reason}}, StateName, D) -> %% Error in initiation. "This error should not occur". - log(error, D, io_lib:format("Shutdown in init (StateName=~p): ~p~n",[StateName,Reason])), + log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]), stop_subsystem(D), close_transport(D); @@ -1952,12 +1959,12 @@ send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> case disconnect_fun(LogMsg, D) of void -> - log(info, D, - io_lib:format("~s~n" - "State = ~p~n" - "Module = ~p, Line = ~p.~n" - "Details:~n ~s~n", - [LogMsg, StateName, Module, Line, DetailedText])); + log(info, D, + "~s~n" + "State = ~p~n" + "Module = ~p, Line = ~p.~n" + "Details:~n ~s~n", + [LogMsg, StateName, Module, Line, DetailedText]); _ -> ok end. @@ -2021,6 +2028,9 @@ fold_keys(Keys, Fun, Extra) -> end, [], Keys). %%%---------------------------------------------------------------- +log(Tag, D, Format, Args) -> + log(Tag, D, io_lib:format(Format,Args)). + log(Tag, D, Reason) -> case atom_to_list(Tag) of % Dialyzer-technical reasons... "error" -> do_log(error_msg, Reason, D); @@ -2028,36 +2038,56 @@ log(Tag, D, Reason) -> "info" -> do_log(info_msg, Reason, D) end. -do_log(F, Reason, #data{ssh_params = #ssh{role = Role} = S - }) -> - VSN = - case application:get_key(ssh,vsn) of - {ok,Vsn} -> Vsn; - undefined -> "" - end, - PeerVersion = - case Role of - server -> S#ssh.c_version; - client -> S#ssh.s_version - end, - CryptoInfo = - try - [{_,_,CI}] = crypto:info_lib(), - <<"(",CI/binary,")">> + +do_log(F, Reason0, #data{ssh_params = S}) -> + Reason = + try io_lib:format("~s",[Reason0]) + of _ -> Reason0 catch - _:_ -> "" - end, - Other = - case Role of - server -> "Client"; - client -> "Server" + _:_ -> io_lib:format("~p",[Reason0]) end, - error_logger:F("Erlang SSH ~p ~s ~s.~n" - "~s: ~p~n" - "~s~n", - [Role, VSN, CryptoInfo, - Other, PeerVersion, - Reason]). + case S of + #ssh{role = Role} when Role==server ; + Role==client -> + {PeerRole,PeerVersion} = + case Role of + server -> {"Client", S#ssh.c_version}; + client -> {"Server", S#ssh.s_version} + end, + error_logger:F("Erlang SSH ~p ~s ~s.~n" + "~s: ~p~n" + "~s~n", + [Role, + ssh_log_version(), crypto_log_info(), + PeerRole, PeerVersion, + Reason]); + _ -> + error_logger:F("Erlang SSH ~s ~s.~n" + "~s~n", + [ssh_log_version(), crypto_log_info(), + Reason]) + end. + +crypto_log_info() -> + try + [{_,_,CI}] = crypto:info_lib(), + case crypto:info_fips() of + enabled -> + <<"(",CI/binary,". FIPS enabled)">>; + not_enabled -> + <<"(",CI/binary,". FIPS available but not enabled)">>; + _ -> + <<"(",CI/binary,")">> + end + catch + _:_ -> "" + end. + +ssh_log_version() -> + case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + undefined -> "" + end. %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index 555080e9ee..1905c40c98 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -37,7 +37,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 1b2ba5a50b..4b6e187c3a 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -92,24 +92,63 @@ -define(XF(S), S#state.xf). -define(REQID(S), S#state.req_id). +-type sftp_option() :: {timeout, timeout()} + | {sftp_vsn, pos_integer()} + | {window_size, pos_integer()} + | {packet_size, pos_integer()} . + +-type reason() :: atom() | string() | tuple() . + %%==================================================================== %% API %%==================================================================== + + +%%%================================================================ +%%% + +%%%---------------------------------------------------------------- +%%% start_channel/1 + start_channel(Cm) when is_pid(Cm) -> start_channel(Cm, []); + start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); -start_channel(Host) when is_list(Host) -> + +start_channel(Host) -> start_channel(Host, []). + +%%%---------------------------------------------------------------- +%%% start_channel/2 + +%%% -spec:s are as if Dialyzer handled signatures for separate +%%% function clauses. + +-spec start_channel(ssh:open_socket(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:connection_ref(), + [sftp_option()] + ) + -> {ok,pid()} | {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:host(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()} . + start_channel(Socket, UserOptions) when is_port(Socket) -> - {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: proplists:get_value(connect_timeout, SshOpts, proplists:get_value(timeout, SftpOpts, infinity)), case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, UserOptions) of + case start_channel(Cm, ChanOpts ++ SftpOpts) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -144,6 +183,16 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> start_channel(Host, UserOptions) -> start_channel(Host, 22, UserOptions). + +%%%---------------------------------------------------------------- +%%% start_channel/3 + +-spec start_channel(ssh:host(), + inet:port_number(), + [ssh:client_option() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}. + start_channel(Host, Port, UserOptions) -> {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: @@ -168,6 +217,15 @@ start_channel(Host, Port, UserOptions) -> Error end. +%%% Helper for start_channel + +wait_for_version_negotiation(Pid, Timeout) -> + call(Pid, wait_for_version_negotiation, Timeout). + +%%%---------------------------------------------------------------- +-spec stop_channel(ChannelPid) -> ok when + ChannelPid :: pid(). + stop_channel(Pid) -> case is_process_alive(Pid) of true -> @@ -185,20 +243,63 @@ stop_channel(Pid) -> ok end. -wait_for_version_negotiation(Pid, Timeout) -> - call(Pid, wait_for_version_negotiation, Timeout). - +%%%---------------------------------------------------------------- +-spec open(ChannelPid, Name, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode) -> open(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open(ChannelPid, Name, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode, FileOpTimeout) -> call(Pid, {open, false, File, Mode}, FileOpTimeout). + +-type tar_crypto_spec() :: encrypt_spec() | decrypt_spec() . + +-type encrypt_spec() :: {init_fun(), crypto_fun(), final_fun()} . +-type decrypt_spec() :: {init_fun(), crypto_fun()} . + +-type init_fun() :: fun(() -> {ok,crypto_state()}) + | fun(() -> {ok,crypto_state(),chunk_size()}) . + +-type crypto_fun() :: fun((TextIn::binary(), crypto_state()) -> crypto_result()) . +-type crypto_result() :: {ok,TextOut::binary(),crypto_state()} + | {ok,TextOut::binary(),crypto_state(),chunk_size()} . + +-type final_fun() :: fun((FinalTextIn::binary(),crypto_state()) -> {ok,FinalTextOut::binary()}) . + +-type chunk_size() :: undefined | pos_integer(). +-type crypto_state() :: any() . + + +-spec open_tar(ChannelPid, Path, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode) -> open_tar(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode, FileOpTimeout) -> case {lists:member(write,Mode), lists:member(read,Mode), - Mode -- [read,write]} of + Mode -- [write,read]} of {true,false,[]} -> {ok,Handle} = open(Pid, File, [write], FileOpTimeout), erl_tar:init(Pid, write, @@ -264,13 +365,33 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> end. +-spec opendir(ChannelPid, Path) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path) -> opendir(Pid, Path, ?FILEOP_TIMEOUT). +-spec opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path, FileOpTimeout) -> call(Pid, {opendir, false, Path}, FileOpTimeout). +-spec close(ChannelPid, Handle) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Error :: {error, reason()} . close(Pid, Handle) -> close(Pid, Handle, ?FILEOP_TIMEOUT). +-spec close(ChannelPid, Handle, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Timeout :: timeout(), + Error :: {error, reason()} . close(Pid, Handle, FileOpTimeout) -> call(Pid, {close,false,Handle}, FileOpTimeout). @@ -279,47 +400,149 @@ readdir(Pid,Handle) -> readdir(Pid,Handle, FileOpTimeout) -> call(Pid, {readdir,false,Handle}, FileOpTimeout). +-spec pread(ChannelPid, Handle, Position, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len) -> pread(Pid, Handle, Offset, Len, ?FILEOP_TIMEOUT). + +-spec pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len, FileOpTimeout) -> call(Pid, {pread,false,Handle, Offset, Len}, FileOpTimeout). + +-spec read(ChannelPid, Handle, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). + +-spec read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). + %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apread(ChannelPid, Handle, Position, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). %% TODO this ought to be a cast! +-spec aread(ChannelPid, Handle, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . aread(Pid, Handle, Len) -> call(Pid, {read,true,Handle, Len}, infinity). + +-spec pwrite(ChannelPid, Handle, Position, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). + +-spec pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Timeout :: timeout(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data, FileOpTimeout) -> call(Pid, {pwrite,false,Handle,Offset,Data}, FileOpTimeout). + +-spec write(ChannelPid, Handle, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Error :: {error, reason()}. write(Pid, Handle, Data) -> write(Pid, Handle, Data, ?FILEOP_TIMEOUT). + +-spec write(ChannelPid, Handle, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec awrite(ChannelPid, Handle, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). +-spec position(ChannelPid, Handle, Location) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos) -> position(Pid, Handle, Pos, ?FILEOP_TIMEOUT). + +-spec position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Timeout :: timeout(), + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos, FileOpTimeout) -> call(Pid, {position, Handle, Pos}, FileOpTimeout). @@ -328,8 +551,21 @@ real_path(Pid, Path) -> real_path(Pid, Path, FileOpTimeout) -> call(Pid, {real_path, false, Path}, FileOpTimeout). + +-spec read_file_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name) -> read_file_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_file_info,false,Name}, FileOpTimeout). @@ -338,18 +574,57 @@ get_file_info(Pid, Handle) -> get_file_info(Pid, Handle, FileOpTimeout) -> call(Pid, {get_file_info,false,Handle}, FileOpTimeout). + +-spec write_file_info(ChannelPid, Name, FileInfo) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info) -> write_file_info(Pid, Name, Info, ?FILEOP_TIMEOUT). + +-spec write_file_info(ChannelPid, Name, FileInfo, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info, FileOpTimeout) -> call(Pid, {write_file_info,false,Name, Info}, FileOpTimeout). + +-spec read_link_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_link_info(Pid, Name) -> read_link_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_link_info,false,Name}, FileOpTimeout). + +-spec read_link(ChannelPid, Name) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()}. read_link(Pid, LinkName) -> read_link(Pid, LinkName, ?FILEOP_TIMEOUT). + +-spec read_link(ChannelPid, Name, Timeout) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link(Pid, LinkName, FileOpTimeout) -> case call(Pid, {read_link,false,LinkName}, FileOpTimeout) of {ok, [{Name, _Attrs}]} -> @@ -358,28 +633,79 @@ read_link(Pid, LinkName, FileOpTimeout) -> ErrMsg end. +-spec make_symlink(ChannelPid, Name, Target) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). +-spec make_symlink(ChannelPid, Name, Target, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). + +-spec rename(ChannelPid, OldName, NewName) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). + +-spec rename(ChannelPid, OldName, NewName, Timeout) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile, FileOpTimeout) -> call(Pid, {rename,false,FromFile, ToFile}, FileOpTimeout). +-spec delete(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . delete(Pid, Name) -> delete(Pid, Name, ?FILEOP_TIMEOUT). +-spec delete(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . delete(Pid, Name, FileOpTimeout) -> call(Pid, {delete,false,Name}, FileOpTimeout). +-spec make_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . make_dir(Pid, Name) -> make_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec make_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_dir(Pid, Name, FileOpTimeout) -> call(Pid, {make_dir,false,Name}, FileOpTimeout). +-spec del_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . del_dir(Pid, Name) -> del_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec del_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . del_dir(Pid, Name, FileOpTimeout) -> call(Pid, {del_dir,false,Name}, FileOpTimeout). @@ -396,9 +722,21 @@ recv_window(Pid, FileOpTimeout) -> call(Pid, recv_window, FileOpTimeout). +-spec list_dir(ChannelPid, Path) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name) -> list_dir(Pid, Name, ?FILEOP_TIMEOUT). - +-spec list_dir(ChannelPid, Path, Timeout) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name, FileOpTimeout) -> case opendir(Pid, Name, FileOpTimeout) of {ok,Handle} -> @@ -429,9 +767,20 @@ do_list_dir(Pid, Handle, FileOpTimeout, Acc) -> end. +-spec read_file(ChannelPid, File) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Error :: {error, reason()}. read_file(Pid, Name) -> read_file(Pid, Name, ?FILEOP_TIMEOUT). +-spec read_file(ChannelPid, File, Timeout) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Timeout :: timeout(), + Error :: {error, reason()}. read_file(Pid, Name, FileOpTimeout) -> case open(Pid, Name, [read, binary], FileOpTimeout) of {ok, Handle} -> @@ -453,9 +802,20 @@ read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) -> Error end. +-spec write_file(ChannelPid, File, Data) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Error :: {error, reason()}. write_file(Pid, Name, List) -> write_file(Pid, Name, List, ?FILEOP_TIMEOUT). +-spec write_file(ChannelPid, File, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file(Pid, Name, List, FileOpTimeout) when is_list(List) -> write_file(Pid, Name, list_to_binary(List), FileOpTimeout); write_file(Pid, Name, Bin, FileOpTimeout) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 5ec12e2d04..bf921f0ff3 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -58,7 +58,17 @@ %%==================================================================== %% API %%==================================================================== --spec subsystem_spec(list()) -> subsystem_spec(). +-spec subsystem_spec(Options) -> Spec when + Options :: [ {cwd, string()} | + {file_handler, CallbackModule::string()} | + {max_files, integer()} | + {root, string()} | + {sftpd_vsn, integer()} + ], + Spec :: {Name, {CbMod,Options}}, + Name :: string(), + CbMod :: atom() . + subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 3aa6e09c2c..05590666da 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -207,6 +207,10 @@ <datatype> <name name="sign_scheme"/> </datatype> + + <datatype> + <name name="group"/> + </datatype> <datatype> <name name="kex_algo"/> @@ -363,7 +367,20 @@ </p> </desc> </datatype> - + + <datatype> + <name name="supported_groups"/> + <desc> + <p>TLS 1.3 introduces the "supported_groups" extension that is used for negotiating + the Diffie-Hellman parameters in a TLS 1.3 handshake. Both client and server + can specify a list of parameters that they are willing to use. + </p> + <p> If it is not specified it will use a default list ([x25519, x448, secp256r1, secp384r1]) that + is filtered based on the installed crypto library version. + </p> + </desc> + </datatype> + <datatype> <name name="secure_renegotiation"/> <desc><p>Specifies if to reject renegotiation attempt that does @@ -919,6 +936,8 @@ fun(srp, Username :: string(), UserState :: term()) -> <name name="dh_der"/> <desc><p>The DER-encoded Diffie-Hellman parameters. If specified, it overrides option <c>dhfile</c>.</p> + <warning><p>The <c>dh_der</c> option is not supported by TLS 1.3. Use the + <c>supported_groups</c> option instead.</p></warning> </desc> </datatype> @@ -928,6 +947,8 @@ fun(srp, Username :: string(), UserState :: term()) -> parameters to be used by the server if a cipher suite using Diffie Hellman key exchange is negotiated. If not specified, default parameters are used.</p> + <warning><p>The <c>dh_file</c> option is not supported by TLS 1.3. Use the + <c>supported_groups</c> option instead.</p></warning> </desc> </datatype> diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index d8c0e30973..4a381745d4 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -255,7 +255,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, + dtls_v1:corresponding_tls_version({Major, Minor})), {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 20b1e85ceb..7ff9aed8ea 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -128,7 +128,8 @@ tls_alert/0, srp_param_type/0, named_curve/0, - sign_scheme/0]). + sign_scheme/0, + group/0]). %% ------------------------------------------------------------------------------------------------------- @@ -243,7 +244,7 @@ secp160r2. % exported -type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 | - ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. + ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported -type srp_param_type() :: srp_1024 | srp_1536 | @@ -296,6 +297,7 @@ {ciphers, cipher_suites()} | {eccs, [named_curve()]} | {signature_algs_cert, signature_schemes()} | + {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | @@ -342,6 +344,7 @@ -type protocol_versions() :: [protocol_version()]. -type signature_algs() :: [{hash(), sign_algo()}]. -type signature_schemes() :: [sign_scheme()]. +-type supported_groups() :: [group()]. -type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: any()}. -type padding_check() :: boolean(). -type beast_mitigation() :: one_n_minus_one | zero_n | disabled. @@ -979,7 +982,8 @@ cipher_suites(all) -> %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version) when Version == 'tlsv1.2'; +cipher_suites(Base, Version) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index f4a91cac52..c16e2331ff 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -38,7 +38,7 @@ cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6, suites/1, all_suites/1, crypto_support_filters/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, - srp_suites/0, srp_suites_anon/0, + srp_suites/1, srp_suites_anon/1, rc4_suites/1, des_suites/1, rsa_suites/1, filter/3, filter_suites/1, filter_suites/2, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, @@ -284,7 +284,7 @@ all_suites({3, _} = Version) -> suites(Version) ++ chacha_suites(Version) ++ psk_suites(Version) - ++ srp_suites() + ++ srp_suites(Version) ++ rc4_suites(Version) ++ des_suites(Version) ++ rsa_suites(Version); @@ -313,8 +313,8 @@ chacha_suites(_) -> %% Description: Returns a list of the anonymous cipher suites, only supported %% if explicitly set by user. Intended only for testing. %%-------------------------------------------------------------------- -anonymous_suites({3, N}) -> - srp_suites_anon() ++ anonymous_suites(N); +anonymous_suites({3, N} = Version) -> + srp_suites_anon(Version) ++ anonymous_suites(N); anonymous_suites({254, _} = Version) -> dtls_v1:anonymous_suites(Version); anonymous_suites(4) -> @@ -375,7 +375,7 @@ psk_suites(_) -> %%-------------------------------------------------------------------- psk_suites_anon({3, N}) -> psk_suites_anon(N); -psk_suites_anon(3) -> +psk_suites_anon(3 = N) -> [ ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, ?TLS_PSK_WITH_AES_256_GCM_SHA384, @@ -401,8 +401,8 @@ psk_suites_anon(3) -> ?TLS_PSK_WITH_AES_128_CCM, ?TLS_PSK_WITH_AES_128_CCM_8, ?TLS_ECDHE_PSK_WITH_RC4_128_SHA - ] ++ psk_suites_anon(0); -psk_suites_anon(_) -> + ] ++ psk_suites_anon(N-1); +psk_suites_anon(N) when N > 0 -> [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA, ?TLS_PSK_WITH_AES_256_CBC_SHA, ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, @@ -413,14 +413,18 @@ psk_suites_anon(_) -> ?TLS_PSK_WITH_3DES_EDE_CBC_SHA, ?TLS_ECDHE_PSK_WITH_RC4_128_SHA, ?TLS_DHE_PSK_WITH_RC4_128_SHA, - ?TLS_PSK_WITH_RC4_128_SHA]. + ?TLS_PSK_WITH_RC4_128_SHA]; +psk_suites_anon(0) -> + []. %%-------------------------------------------------------------------- --spec srp_suites() -> [ssl_cipher_format:cipher_suite()]. +-spec srp_suites(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the SRP cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites() -> +srp_suites({3,0}) -> + []; +srp_suites(_) -> [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, @@ -429,12 +433,14 @@ srp_suites() -> ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. %%-------------------------------------------------------------------- --spec srp_suites_anon() -> [ssl_cipher_format:cipher_suite()]. +-spec srp_suites_anon(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the SRP anonymous cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites_anon() -> +srp_suites_anon({3,0}) -> + []; +srp_suites_anon(_) -> [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA, ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA]. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index de12de646c..2483509228 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1275,10 +1275,17 @@ connection({call, From}, {connection_information, false}, State, _) -> Info = connection_info(State), hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = undefined}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{negotiated_protocol = SelectedProtocol}} = State, _) -> + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = SelectedProtocol}} = State, _) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, Msg, State, Connection) -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index c6698bc74a..bd2efa9fbb 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -58,7 +58,7 @@ ]). %% Encode --export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, +-export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode -export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3, @@ -534,14 +534,14 @@ encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; -encode_handshake(#server_hello{server_version = {Major, Minor}, +encode_handshake(#server_hello{server_version = {Major, Minor} = Version, random = Random, session_id = Session_ID, cipher_suite = CipherSuite, compression_method = Comp_method, extensions = Extensions}, _Version) -> SID_length = byte_size(Session_ID), - ExtensionsBin = encode_hello_extensions(Extensions), + ExtensionsBin = encode_hello_extensions(Extensions, Version), {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID/binary, CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>}; @@ -589,7 +589,9 @@ encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = Ha encode_handshake(#finished{verify_data = VerifyData}, _Version) -> {?FINISHED, VerifyData}. -encode_hello_extensions(Extensions) -> +encode_hello_extensions(_, {3, 0}) -> + <<>>; +encode_hello_extensions(Extensions, _) -> encode_extensions(hello_extensions_list(Extensions), <<>>). encode_extensions(Exts) -> @@ -707,7 +709,25 @@ encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> Group = tls_v1:group_to_enum(Group0), encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), - ?UINT16(2), ?UINT16(Group), Acc/binary>>). + ?UINT16(2), ?UINT16(Group), Acc/binary>>); +encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> + KEModes = encode_psk_key_exchange_modes(KEModes0), + KEModesLen = byte_size(KEModes), + ExtLen = KEModesLen + 1, + encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), + ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = Identities0, + binders = Binders0} = PSKs} | Rest], Acc) -> + Identities = encode_psk_identities(Identities0), + Binders = encode_psk_binders(Binders0), + Len = byte_size(Identities) + byte_size(Binders), + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(Len), Identities/binary, Binders/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(2), ?UINT16(Identity), Acc/binary>>). encode_client_protocol_negotiation(undefined, _) -> @@ -1256,6 +1276,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). [Protocol] when not Renegotiation -> {ConnectionStates, alpn, Protocol}; + [_] when Renegotiation -> + {ConnectionStates, alpn, undefined}; undefined -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), @@ -1486,8 +1508,12 @@ extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) -> Schemes; extension_value(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; +extension_value(#key_share_server_hello{server_share = ServerShare}) -> + ServerShare; extension_value(#client_hello_versions{versions = Versions}) -> - Versions. + Versions; +extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) -> + SelectedVersion. %%-------------------------------------------------------------------- @@ -2089,6 +2115,41 @@ encode_key_share_entry(#key_share_entry{ Len = byte_size(KeyExchange), <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>. +encode_psk_key_exchange_modes(KEModes) -> + encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>). +%% +encode_psk_key_exchange_modes([], Acc) -> + Acc; +encode_psk_key_exchange_modes([psk_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>); +encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>). + + +encode_psk_identities(Identities) -> + encode_psk_identities(Identities, <<>>). +%% +encode_psk_identities([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_identities([#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|T], Acc) -> + IdLen = byte_size(Identity), + encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,Age/binary>>). + + +encode_psk_binders(Binders) -> + encode_psk_binders(Binders, <<>>). +%% +encode_psk_binders([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_binders([Binder|T], Acc) -> + Len = byte_size(Binder), + encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>). + + hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2441,6 +2502,33 @@ decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), #key_share_hello_retry_request{ selected_group = tls_v1:enum_to_group(Group)}}); +decode_extensions(<<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> + <<?BYTE(PLen),KEModes:PLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{psk_key_exchange_modes => + #psk_key_exchange_modes{ + ke_modes = decode_psk_key_exchange_modes(KEModes)}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = client_hello, Acc) -> + <<?UINT16(IdLen),Identities:IdLen/binary,?UINT16(BLen),Binders:BLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = decode_psk_identities(Identities), + binders = decode_psk_binders(Binders)}}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = server_hello, Acc) -> + <<?UINT16(Identity)>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_server_hello{ + selected_identity = Identity}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. @@ -2500,6 +2588,38 @@ decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> decode_protocols(_Bytes, _Acc) -> {error, invalid_protocols}. + +decode_psk_key_exchange_modes(KEModes) -> + decode_psk_key_exchange_modes(KEModes, []). +%% +decode_psk_key_exchange_modes(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_DHE_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]). + + +decode_psk_identities(Identities) -> + decode_psk_identities(Identities, []). +%% +decode_psk_identities(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, Age:4/binary, Rest/binary>>, Acc) -> + decode_psk_identities(Rest, [#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|Acc]). + + +decode_psk_binders(Binders) -> + decode_psk_binders(Binders, []). +%% +decode_psk_binders(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) -> + decode_psk_binders(Rest, [Binder|Acc]). + + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -2666,7 +2786,7 @@ filter_unavailable_ecc_suites(_, Suites) -> handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation) -> - {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + {ok, ConnectionStates} = handle_renegotiation_info(Version, RecordCB, Role, Info, ConnectionStates0, Renegotiation, SecureRenegotation, ClientCipherSuites), hello_pending_connection_states(RecordCB, Role, @@ -2936,11 +3056,11 @@ renegotiation_info(_RecordCB, server, ConnectionStates, true) -> #renegotiation_info{renegotiated_connection = undefined} end. -handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, +handle_renegotiation_info(_, _RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; -handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> +handle_renegotiation_info(_, _RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; @@ -2948,10 +3068,10 @@ handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} end; -handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> +handle_renegotiation_info(_, _RecordCB, _, undefined, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; -handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, +handle_renegotiation_info(_, _RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, ConnectionStates, true, _, _) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), CData = maps:get(client_verify_data, ConnectionState), @@ -2962,7 +3082,7 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co false -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)) end; -handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, +handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of @@ -2978,11 +3098,13 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; +handle_renegotiation_info({3,0}, _RecordCB, client, undefined, ConnectionStates, true, _SecureRenegotation, _) -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; -handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> +handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); -handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> +handle_renegotiation_info(_, RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); @@ -3036,7 +3158,7 @@ empty_extensions({3,4}, client_hello) -> %% padding => undefined, key_share => undefined, pre_shared_key => undefined, - %% psk_key_exhange_modes => undefined, + psk_key_exchange_modes => undefined, %% early_data => undefined, %% cookie => undefined, client_hello_versions => undefined, @@ -3065,6 +3187,8 @@ empty_extensions({3,4}, hello_retry_request) -> key_share => undefined, pre_shared_key => undefined }; +empty_extensions({3,0}, _) -> + empty_extensions(); empty_extensions(_, server_hello) -> #{renegotiation_info => undefined, alpn => undefined, diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index c132f75eae..37265e0759 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -379,7 +379,7 @@ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) - %%-------------------------------------------------------------------- enc_handshake(#hello_request{}, {3, N}) when N < 4 -> {?HELLO_REQUEST, <<>>}; -enc_handshake(#client_hello{client_version = {Major, Minor}, +enc_handshake(#client_hello{client_version = {Major, Minor} = Version, random = Random, session_id = SessionID, cipher_suites = CipherSuites, @@ -390,7 +390,7 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version), {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 49d20b3ec0..c29366e717 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -39,7 +39,7 @@ %% Create handshake messages -export([certificate/5, certificate_verify/4, - encrypted_extensions/0]). + encrypted_extensions/1]). -export([do_start/2, do_negotiated/2, @@ -61,10 +61,10 @@ %% Create handshake messages %%==================================================================== -server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> +server_hello(MsgType, SessionId, KeyShare, ConnectionStates) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - Extensions = server_hello_extensions(MsgType, KeyShare, ALPN), + Extensions = server_hello_extensions(MsgType, KeyShare), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = 0, %% legacy attribute @@ -73,6 +73,7 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> extensions = Extensions }. + %% The server's extensions MUST contain "supported_versions". %% Additionally, it SHOULD contain the minimal set of extensions %% necessary for the client to generate a correct ClientHello pair. As @@ -80,18 +81,14 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> %% extensions that were not first offered by the client in its %% ClientHello, with the exception of optionally the "cookie" (see %% Section 4.2.2) extension. -server_hello_extensions(hello_retry_request = MsgType, KeyShare, _) -> +server_hello_extensions(hello_retry_request = MsgType, KeyShare) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, ssl_handshake:add_server_share(MsgType, Extensions, KeyShare); -server_hello_extensions(MsgType, KeyShare, undefined) -> +server_hello_extensions(MsgType, KeyShare) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, - ssl_handshake:add_server_share(MsgType, Extensions, KeyShare); -server_hello_extensions(MsgType, KeyShare, ALPN0) -> - Extensions0 = ssl_handshake:add_selected_version(#{}), %% {3,4} (TLS 1.3) - Extensions1 = ssl_handshake:add_alpn(Extensions0, ALPN0), - ssl_handshake:add_server_share(MsgType, Extensions1, KeyShare). + ssl_handshake:add_server_share(MsgType, Extensions, KeyShare). server_hello_random(server_hello, #security_parameters{server_random = Random}) -> @@ -107,10 +104,14 @@ server_hello_random(hello_retry_request, _) -> ?HELLO_RETRY_REQUEST_RANDOM. -%% TODO: implement support for encrypted_extensions -encrypted_extensions() -> +encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) -> #encrypted_extensions{ extensions = #{} + }; +encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) -> + Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol), + #encrypted_extensions{ + extensions = Extensions }. @@ -503,7 +504,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ssl_options = #ssl_options{ciphers = ServerCiphers, signature_algs = ServerSignAlgs, supported_groups = ServerGroups0, - alpn_preferred_protocols = ALPNPreferredProtocols}, + alpn_preferred_protocols = ALPNPreferredProtocols, + honor_cipher_order = HonorCipherOrder}, session = #session{own_certificate = Cert}} = State0) -> ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), @@ -530,7 +532,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% cipher suite, an (EC)DHE group and key share for key establishment, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. - Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), + Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), @@ -673,8 +675,7 @@ do_negotiated(start_handshake, dh_public_value = ClientPublicKey}, ssl_options = #ssl_options{} = SslOpts, key_share = KeyShare, - handshake_env = #handshake_env{tls_handshake_history = _HHistory0, - alpn = ALPN}, + handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, connection_env = #connection_env{private_key = CertPrivateKey}, static_env = #static_env{ cert_db = CertDbHandle, @@ -689,7 +690,7 @@ do_negotiated(start_handshake, try %% Create server_hello %% Extensions: supported_versions, key_share, (pre_shared_key) - ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0, ALPN), + ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), @@ -699,7 +700,7 @@ do_negotiated(start_handshake, State3 = ssl_record:step_encryption_state(State2), %% Create EncryptedExtensions - EncryptedExtensions = encrypted_extensions(), + EncryptedExtensions = encrypted_extensions(State2), %% Encode EncryptedExtensions State4 = tls_connection:queue_handshake(EncryptedExtensions, State3), @@ -881,12 +882,19 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, end. -do_wait_ee(#encrypted_extensions{extensions = _Extensions}, State0) -> +do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> + + ALPNProtocol0 = maps:get(alpn, Extensions, undefined), + ALPNProtocol = get_alpn(ALPNProtocol0), {Ref,_Maybe} = maybe(), try - {State0, wait_cert_cr} + %% Update state + #state{handshake_env = HsEnv} = State0, + State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, + + {State1, wait_cert_cr} catch {Ref, {insufficient_security, no_suitable_groups}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); @@ -1041,10 +1049,9 @@ compare_verify_data(_, _) -> {error, decrypt_error}. -send_hello_retry_request(#state{connection_states = ConnectionStates0, - handshake_env = #handshake_env{alpn = ALPN}} = State0, +send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, no_suitable_key, KeyShare, SessionId) -> - ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0, ALPN), + ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), %% Update handshake history @@ -1725,15 +1732,19 @@ handle_alpn([ServerProtocol|T], ClientProtocols) -> end. -select_cipher_suite([], _) -> +select_cipher_suite(_, [], _) -> {error, no_suitable_cipher}; -select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> +%% If honor_cipher_order is set to true, use the server's preference for +%% cipher suite selection. +select_cipher_suite(true, ClientCiphers, ServerCiphers) -> + select_cipher_suite(false, ServerCiphers, ClientCiphers); +select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> - select_cipher_suite(ClientCiphers, ServerCiphers) + select_cipher_suite(false, ClientCiphers, ServerCiphers) end. @@ -1869,6 +1880,14 @@ get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) -> SelectedGroup. +get_alpn(ALPNProtocol0) -> + case ssl_handshake:decode_alpn(ALPNProtocol0) of + undefined -> + undefined; + [ALPNProtocol] -> + ALPNProtocol + end. + maybe() -> Ref = erlang:make_ref(), Ok = fun(ok) -> ok; diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 7ae1b93e1c..eb85f216c8 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -74,29 +74,52 @@ y % opaque Y[coordinate_length]; }). +%% RFC 8446 4.2.9. Pre-Shared Key Exchange Modes + +%% enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; -define(PSK_KE, 0). -define(PSK_DHE_KE, 1). --record(psk_keyexchange_modes, { +-record(psk_key_exchange_modes, { ke_modes % ke_modes<1..255> }). + +%% RFC 8446 4.2.10. Early Data Indication -record(empty, { }). -record(early_data_indication, { indication % uint32 max_early_data_size (new_session_ticket) | %% #empty{} (client_hello, encrypted_extensions) }). --record(psk_identity, { - identity, % opaque identity<1..2^16-1> - obfuscated_ticket_age % uint32 - }). --record(offered_psks, { - psk_identity, %identities<7..2^16-1>; - psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255> - }). --record(pre_shared_keyextension,{ - extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello) - }). + +%% RFC 8446 4.2.11. Pre-Shared Key Extension +-record(psk_identity, + { + identity, % opaque identity<1..2^16-1> + obfuscated_ticket_age % uint32 + }). + +-record(offered_psks, + { + identities, % PskIdentity identities<7..2^16-1>; + binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255> + }). + +%% struct { +%% select (Handshake.msg_type) { +%% case client_hello: OfferedPsks; +%% case server_hello: uint16 selected_identity; +%% }; +%% } PreSharedKeyExtension; +-record(pre_shared_key_client_hello, + { + offered_psks + }). + +-record(pre_shared_key_server_hello, + { + selected_identity + }). %% RFC 8446 B.3.1.2. -record(cookie, { diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 74321a1ae2..d713062284 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -138,6 +138,15 @@ decode_cipher_text(#ssl_tls{type = ?ALERT, {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version fragment = <<2,47>>}, ConnectionStates0}; +%% TLS 1.3 server can receive a User Cancelled Alert when handshake is +%% paused and then cancelled on the client side. +decode_cipher_text(#ssl_tls{type = ?ALERT, + version = ?LEGACY_VERSION, + fragment = <<2,90>>}, + ConnectionStates0) -> + {#ssl_tls{type = ?ALERT, + version = {3,4}, %% Internally use real version + fragment = <<2,90>>}, ConnectionStates0}; %% RFC8446 - TLS 1.3 %% D.4. Middlebox Compatibility Mode %% - If not offering early data, the client sends a dummy diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index dba90aaff0..0925c0facc 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -37,14 +37,29 @@ VSN=$(SSL_VSN) MODULES = \ ssl_test_lib \ + ssl_app_env_SUITE\ + ssl_alert_SUITE\ ssl_bench_test_lib \ ssl_dist_test_lib \ - ssl_alpn_handshake_SUITE \ + ssl_api_SUITE\ + tls_api_SUITE\ ssl_basic_SUITE \ ssl_bench_SUITE \ ssl_cipher_SUITE \ ssl_cipher_suite_SUITE \ - openssl_server_cipher_suite_SUITE\ + openssl_cipher_suite_SUITE\ + ssl_alpn_SUITE \ + openssl_alpn_SUITE\ + ssl_npn_SUITE \ + openssl_npn_SUITE\ + openssl_sni_SUITE\ + ssl_renegotiate_SUITE\ + openssl_renegotiate_SUITE\ + openssl_reject_SUITE\ + ssl_cert_tests\ + ssl_cert_SUITE\ + openssl_server_cert_SUITE\ + openssl_client_cert_SUITE\ ssl_certificate_verify_SUITE\ ssl_crl_SUITE\ ssl_dist_SUITE \ @@ -52,12 +67,12 @@ MODULES = \ ssl_engine_SUITE\ ssl_handshake_SUITE \ ssl_npn_hello_SUITE \ - ssl_npn_handshake_SUITE \ ssl_packet_SUITE \ ssl_payload_SUITE \ ssl_pem_cache_SUITE \ + ssl_session_SUITE \ ssl_session_cache_SUITE \ - ssl_to_openssl_SUITE \ + openssl_session_SUITE \ ssl_ECC_SUITE \ ssl_ECC_openssl_SUITE \ ssl_ECC\ @@ -65,6 +80,10 @@ MODULES = \ ssl_sni_SUITE \ ssl_eqc_SUITE \ ssl_rfc_5869_SUITE \ + tls_1_3_record_SUITE\ + openssl_tls_1_3_version_SUITE\ + tls_1_3_version_SUITE\ + ssl_socket_SUITE\ make_certs \ x509_test \ inet_crypto_dist diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl new file mode 100644 index 0000000000..5008dba922 --- /dev/null +++ b/lib/ssl/test/openssl_alpn_SUITE.erl @@ -0,0 +1,421 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_alpn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: ALPN not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls_alpn() of + true -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls_alpn() of + true -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'dtlsv1.2', [], alpn_tests()}, + {'dtlsv1', [], alpn_tests()} + ]; + false -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()} + ] + end. + +alpn_tests() -> + [erlang_client_alpn_openssl_server_alpn, + erlang_server_alpn_openssl_client_alpn, + erlang_client_alpn_openssl_server, + erlang_client_openssl_server_alpn, + erlang_server_alpn_openssl_client, + erlang_server_openssl_client_alpn]. + +alpn_npn_coexist() -> + [ + erlang_client_alpn_npn_openssl_server_alpn_npn, + erlang_server_alpn_npn_openssl_client_alpn_npn + ]. +rengotiation_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_client_alpn_openssl_server_alpn_renegotiate, + erlang_server_alpn_openssl_client_alpn_renegotiate]; + false -> + [] + end. +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_alpn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; + TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; + TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> + ssl_test_lib:check_openssl_npn_support(Config); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_alpn_openssl_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{alpn_advertised_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_alpn_openssl_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{alpn_preferred_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- +check_openssl_alpn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "alpn") of + 0 -> + {skip, "Openssl not compiled with alpn support"}; + _ -> + Config + end. + +start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + 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 = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + 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, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + + 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). + +start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", + "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + + 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, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, + {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", + "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + 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). + diff --git a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl index 0d68d84d61..955eb914c0 100644 --- a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl +++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl @@ -20,7 +20,7 @@ %% --module(openssl_server_cipher_suite_SUITE). +-module(openssl_cipher_suite_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -31,19 +31,26 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [ - {group, 'tlsv1.2'}, + [ + {group, openssl_server}, + {group, openssl_client} + ]. + +all_protocol_groups() -> + [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'sslv3'}, {group, 'dtlsv1.2'}, {group, 'dtlsv1'} - ]. + ]. groups() -> %% TODO: Enable SRP, PSK suites (needs OpenSSL s_server conf) %% TODO: Enable all "kex" on DTLS [ + {openssl_server, all_protocol_groups()}, + {openssl_client, all_protocol_groups()}, {'tlsv1.2', [], kex()}, {'tlsv1.1', [], kex()}, {'tlsv1', [], kex()}, @@ -135,6 +142,9 @@ kex() -> dtls_kex() -> %% Should be all kex in the future dtls_rsa() ++ dss() ++ anonymous(). +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + rsa() -> [{group, dhe_rsa}, {group, ecdhe_rsa}, @@ -148,6 +158,11 @@ dtls_rsa() -> %%,{group, rsa_psk} ]. +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + ecdsa() -> [{group, ecdhe_ecdsa}]. @@ -156,6 +171,10 @@ dss() -> %%{group, srp_dss} ]. +ssl3_dss() -> + [{group, dhe_dss} + ]. + anonymous() -> [{group, dh_anon}, {group, ecdh_anon} @@ -165,6 +184,9 @@ anonymous() -> %%{group, srp_anon} ]. +ssl3_anonymous() -> + [{group, dh_anon}]. + init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of @@ -199,7 +221,12 @@ init_per_group(GroupName, Config) -> false -> do_init_per_group(GroupName, Config) end. - +do_init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +do_init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; do_init_per_group(GroupName, Config) when GroupName == ecdh_anon; GroupName == ecdhe_rsa; GroupName == ecdhe_psk -> @@ -747,8 +774,7 @@ cipher_suite_test(CipherSuite, _Version, Config) -> ct:log("Testing CipherSuite ~p~n", [CipherSuite]), ct:log("Server Opts ~p~n", [ServerOpts]), ct:log("Client Opts ~p~n", [ClientOpts]), - ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, [{client_type, erlang}, - {server_type, openssl} | Config]). + ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config). test_ciphers(Kex, Cipher, Version) -> diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl new file mode 100644 index 0000000000..b327988744 --- /dev/null +++ b/lib/ssl/test/openssl_client_cert_SUITE.erl @@ -0,0 +1,350 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_client_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, openssl_client} + ]. + +groups() -> + [ + {openssl_client, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. +init_per_testcase(TestCase, Config) when + TestCase == client_auth_empty_cert_accepted; + TestCase == client_auth_empty_cert_rejected -> + Version = proplists:get_value(version,Config), + case Version of + sslv3 -> + %% Openssl client sends "No Certificate Reserved" warning ALERT + %% instead of sending EMPTY cert message in SSL-3.0 so empty cert test are not + %% relevant + {skip, openssl_behaves_differently}; + _ -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config + end; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl new file mode 100644 index 0000000000..0294f4997f --- /dev/null +++ b/lib/ssl/test/openssl_npn_SUITE.erl @@ -0,0 +1,311 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_npn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + %% NPN is not supported in TLS-1.3 (replaced by ALPN and deprecated in TLS 1.2) + %% OpenSSL DTLS support for NPN is either not there or broken. + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}]. + +groups() -> + [{'tlsv1.2', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1.1', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1', [], npn_tests() ++ npn_renegotiate_tests()} + ]. + +npn_tests() -> + [erlang_client_openssl_server_npn, + erlang_server_openssl_client_npn, + erlang_server_openssl_client_npn_only_client, + erlang_server_openssl_client_npn_only_server, + erlang_client_openssl_server_npn_only_client, + erlang_client_openssl_server_npn_only_server]. + +npn_renegotiate_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_server_openssl_client_npn_renegotiate, + erlang_client_openssl_server_npn_renegotiate]; + false -> + [] + end. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_npn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_npn_openssl_server_npn_renegotiate; + TestCase == erlang_server_npn_openssl_client_npn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_npn() -> + [{doc,"Test erlang client with openssl server doing npn negotiation"}]. + +erlang_client_openssl_server_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_npn_renegotiate() -> + [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. + +erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn() -> + [{doc,"Test erlang server with openssl client and npn negotiation"}]. + +erlang_server_openssl_client_npn(Config) when is_list(Config) -> + + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_renegotiate() -> + [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. + +erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). +%%-------------------------------------------------------------------------- +erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, [], + ["-nextprotoneg", "spdy/2"], Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{client_preferred_next_protocols, + {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{next_protocols_advertised, [<<"spdy/2">>]}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +start_erlang_client_and_openssl_server_for_npn_negotiation(Config, 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 = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | 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 = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + 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, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", + ssl_test_lib: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). + +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. diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl new file mode 100644 index 0000000000..deefd11823 --- /dev/null +++ b/lib/ssl/test/openssl_reject_SUITE.erl @@ -0,0 +1,209 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_reject_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_GARBAGE, "P\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ]. + +all_versions_tests() -> + [ + erlang_client_bad_openssl_server, + ssl2_erlang_server_openssl_client + ]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(ssl2_erlang_server_openssl_client, Config) -> + case ssl_test_lib:supports_ssl_tls_version(sslv2) of + true -> + Config; + false -> + {skip, "sslv2 not supported by openssl"} + end; +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_client_bad_openssl_server() -> + [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. +erlang_client_bad_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Send garbage + true = port_command(OpensslPort, ?OPENSSL_GARBAGE), + + ct:sleep(?SLEEP), + + Client0 ! server_sent_garbage, + + ssl_test_lib:check_result(Client0, true), + + ssl_test_lib:close(Client0), + + %% Make sure openssl does not hang and leave zombie process + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client1), + process_flag(trap_exit, false). + +%%-------------------------------------------------------------------- +ssl2_erlang_server_openssl_client() -> + [{doc,"Test that ssl v2 clients are rejected"}]. + +ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + "-ssl2", "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), + ssl_test_lib:consume_port_exit(OpenSslPort), + ssl_test_lib:check_server_alert(Server, unexpected_message), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +server_sent_garbage(Socket) -> + receive + server_sent_garbage -> + {error, closed} == ssl:send(Socket, "data") + + end. diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..787b5208b8 --- /dev/null +++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl @@ -0,0 +1,342 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_renegotiate_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_RENEGOTIATE, "R\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()}, + {'dtlsv1.2', [], all_versions_tests()}, + {'dtlsv1', [], all_versions_tests()} + ]; + false -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ] + end. + +all_versions_tests() -> + [ + erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_renegotiate_after_client_data, + erlang_client_openssl_server_nowrap_seqnum, + erlang_server_openssl_client_nowrap_seqnum + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:check_sane_openssl_renegotaite(ssl_test_lib:init_tls_version(GroupName, Config), + GroupName); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_openssl_server_renegotiate; + TestCase == erlang_client_openssl_server_nowrap_seqnum; + TestCase == erlang_server_openssl_client_nowrap_seqnum; + TestCase == erlang_client_openssl_server_renegotiate_after_client_data + -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_renegotiate() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. +erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + 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, + delayed_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% 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), + ok. +%%-------------------------------------------------------------------- +erlang_client_openssl_server_renegotiate_after_client_data() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. +erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + 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 = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + 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, + send_wait_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} |ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% 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), + ok. + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl\n", + N = 10, + + Port = ssl_test_lib:inet_port(node()), + 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 = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + 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, {ssl_test_lib, + trigger_renegotiate, [[ErlData, N+2]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + + %% 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). +%%-------------------------------------------------------------------- +erlang_server_openssl_client_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + N = 10, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client","-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +delayed_send(Socket, [ErlData, OpenSslData]) -> + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + + +send_wait_send(Socket, [ErlData, OpenSslData]) -> + ssl:send(Socket, ErlData), + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl new file mode 100644 index 0000000000..c2af864a92 --- /dev/null +++ b/lib/ssl/test/openssl_server_cert_SUITE.erl @@ -0,0 +1,373 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_server_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, openssl_server}]. + +groups() -> + [ + {openssl_server, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}, + %% TODO: Create proper conf of openssl server + %%++ [unsupported_sign_algo_client_auth, + %% unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(rsa_1_3 = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(ecdsa = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(ecdsa_1_3 = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl new file mode 100644 index 0000000000..97d83b98c3 --- /dev/null +++ b/lib/ssl/test/openssl_session_SUITE.erl @@ -0,0 +1,258 @@ +%% +%% %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(openssl_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], tests()}, + {'dtlsv1', [], tests()} + ]; + false -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()} + ] + end. + +tests() -> + [ + reuse_session_erlang_server, + reuse_session_erlang_client + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_erlang_client, Config) -> + ct:timetrap(?EXPIRE * 1000 * 5), + ssl:stop(), + application:load(ssl), + application:set_env(ssl, session_lifetime, ?EXPIRE), + ssl:start(), + Config; + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(reuse_session_erlang_client, Config) -> + application:unset_env(ssl, session_lifetime), + Config; +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +reuse_session_erlang_server() -> + [{doc, "Test erlang server with openssl client that reconnects with the" + "same session id, to test reusing of sessions."}]. +reuse_session_erlang_server(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, active_recv, [length(Data)]}}, + {reconnect_times, 5}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) + ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-reconnect"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, Data), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort). + +%%-------------------------------------------------------------------- + +reuse_session_erlang_client() -> + [{doc, "Test erlang ssl client that wants to reuse sessions"}]. +reuse_session_erlang_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Version = ssl_test_lib:protocol_version(Config), + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CACertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + ssl_test_lib:close(Client0), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]), + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + + ssl_test_lib:close(Client1), + %% Make sure session is unregistered due to expiration + ct:sleep(20000), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, ID} -> + case ID of + SID -> + ct:fail(expired_session_reused); + _ -> + ok + end + end, + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client2). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl new file mode 100644 index 0000000000..26f08e36c0 --- /dev/null +++ b/lib/ssl/test/openssl_sni_SUITE.erl @@ -0,0 +1,251 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_sni_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: SNI not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'} + %% Seems broken in openssl + %%{group, 'dtlsv1.2'}, + %%{group, 'dtlsv1'} + ]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + %% Seems broken in openssl + %%{'dtlsv1.2', [], sni_tests()}, + %%{'dtlsv1', [], sni_tests()} + ]; + false -> + [{'tlsv1.2', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + ] + end. + +sni_tests() -> + [erlang_server_openssl_client_sni_match, + erlang_server_openssl_client_sni_match_fun, + erlang_server_openssl_client_sni_no_match, + erlang_server_openssl_client_sni_no_match_fun, + erlang_server_openssl_client_sni_no_header, + erlang_server_openssl_client_sni_no_header_fun]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_sni_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + [{sni_server_opts, [{sni_hosts, + [{"a.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]}, + {"b.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]} + ]}]} | Config] + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). + + +erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server), + ok. + + +erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server). + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + case ssl:connection_information(SSLSocket, [sni_hostname]) of + {ok, []} -> + undefined; + {ok, [{sni_hostname, Hostname}]} -> + Hostname + end. + +openssl_client_args(Version, Hostname, Port) -> + ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. + +openssl_client_args(Version, Hostname, Port, ServerName) -> + ["s_client", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. + +check_openssl_sni_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case ssl_test_lib:is_sane_oppenssl_client() of + true -> + case string:str(HelpText, "-servername") of + 0 -> + {skip, "Current openssl doesn't support SNI"}; + _ -> + Config + end; + false -> + {skip, "Current openssl doesn't support SNI or extension handling is flawed"} + end. diff --git a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..8a2692ec1d --- /dev/null +++ b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl @@ -0,0 +1,172 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(openssl_tls_1_3_version_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + %%{group, openssl_server}, + {group, openssl_client} + ]. + +groups() -> + [ + %%{openssl_server, [{group, 'tlsv1.3'}]}, + {openssl_client, [{group, 'tlsv1.3'}]}, + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + %%tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case ssl_test_lib:check_sane_openssl_version('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, openssl_does_not_support_version} + end + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%% tls13_client_with_ext_tls12_server() -> +%% [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " +%% "client has TLS 1.3 specsific extensions"}]. + +%% tls13_client_with_ext_tls12_server(Config) -> +%% ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), +%% ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + +%% {ServerOpts, ClientOpts} = +%% case proplists:get_value(client_type) of +%% erlang -> +%% {[{versions, ['tlsv1.2']}|ServerOpts0], +%% [{versions, ['tlsv1.2','tlsv1.3']}, +%% {signature_algs_cert, [ecdsa_secp384r1_sha384, +%% ecdsa_secp256r1_sha256, +%% rsa_pss_rsae_sha256, +%% rsa_pkcs1_sha256, +%% {sha256,rsa},{sha256,dsa}]}|ClientOpts0]}; +%% openssl -> + + +%% ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 38a4b7fb11..2ceb540e15 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -119,19 +119,27 @@ tls_msg(Version) -> %% client_hello(?'TLS_v1.3' = Version) -> #client_hello{session_id = session_id(), - client_version = ?'TLS_v1.2', - cipher_suites = cipher_suites(Version), + client_version = ?'TLS_v1.2', + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = client_hello_extensions(Version) + }; +client_hello(Version) -> + #client_hello{session_id = session_id(), + client_version = Version, + cipher_suites = cipher_suites(Version), compression_methods = compressions(Version), random = client_random(Version), extensions = client_hello_extensions(Version) }; -client_hello(Version) -> +client_hello(?'SSL_v3' = Version) -> #client_hello{session_id = session_id(), client_version = Version, cipher_suites = cipher_suites(Version), compression_methods = compressions(Version), random = client_random(Version), - extensions = client_hello_extensions(Version) + extensions = ssl_handshake:empty_extensions(Version, client_hello) }. server_hello(?'TLS_v1.3' = Version) -> @@ -142,6 +150,14 @@ server_hello(?'TLS_v1.3' = Version) -> compression_method = compression(Version), extensions = server_hello_extensions(Version) }; +server_hello(?'SSL_v3' = Version) -> + #server_hello{server_version = Version, + session_id = session_id(), + random = server_random(Version), + cipher_suite = cipher_suite(Version), + compression_method = compression(Version), + extensions = ssl_handshake:empty_extensions(Version, server_hello) + }; server_hello(Version) -> #server_hello{server_version = Version, session_id = session_id(), @@ -291,7 +307,7 @@ pre_shared_keyextension() -> %% | | | %% | signature_algorithms_cert (RFC 8446) | CH, CR | %% +--------------------------------------------------+-------------+ -extensions(?'TLS_v1.3' = Version, client_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = client_hello) -> ?LET({ ServerName, %% MaxFragmentLength, @@ -306,8 +322,8 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% ServerCertificateType, %% Padding, KeyShare, - %% PreSharedKey, - %% PSKKeyExchangeModes, + PreSharedKey, + PSKKeyExchangeModes, %% EarlyData, %% Cookie, SupportedVersions, @@ -328,9 +344,9 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([client_cert_type(), undefined]), %% oneof([server_cert_type(), undefined]), %% oneof([padding(), undefined]), - oneof([key_share(client_hello), undefined]), - %% oneof([pre_shared_key(), undefined]), - %% oneof([psk_key_exchange_modes(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), + oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), %% oneof([cookie(), undefined]), oneof([client_hello_versions(Version)]), @@ -357,8 +373,8 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% server_cert_type => ServerCertificateType, %% padding => Padding, key_share => KeyShare, - %% pre_shared_key => PreSharedKey, - %% psk_key_exhange_modes => PSKKeyExchangeModes, + pre_shared_key => PreSharedKey, + psk_key_exchange_modes => PSKKeyExchangeModes, %% early_data => EarlyData, %% cookie => Cookie, client_hello_versions => SupportedVersions, @@ -401,15 +417,15 @@ extensions(Version, client_hello) -> srp => SRP %% renegotiation_info => RenegotiationInfo })); -extensions(?'TLS_v1.3' = Version, server_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = server_hello) -> ?LET({ KeyShare, - %% PreSharedKeys, + PreSharedKey, SupportedVersions }, { - oneof([key_share(server_hello), undefined]), - %% oneof([pre_shared_keys(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), oneof([server_hello_selected_version()]) }, maps:filter(fun(_, undefined) -> @@ -419,7 +435,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) -> end, #{ key_share => KeyShare, - %% pre_shared_keys => PreSharedKeys, + pre_shared_key => PreSharedKey, server_hello_selected_version => SupportedVersions })); extensions(Version, server_hello) -> @@ -810,3 +826,58 @@ group_list(N, Pool, Acc) -> R = rand:uniform(length(Pool)), G = lists:nth(R, Pool), group_list(N - 1, Pool -- [G], [G|Acc]). + + +ke_modes() -> + oneof([[psk_ke],[psk_dhe_ke],[psk_ke,psk_dhe_ke]]). + +psk_key_exchange_modes() -> + ?LET(KEModes, ke_modes(), + #psk_key_exchange_modes{ + ke_modes = KEModes}). + +pre_shared_key(client_hello) -> + ?LET(OfferedPsks, offered_psks(), + #pre_shared_key_client_hello{ + offered_psks = OfferedPsks}); +pre_shared_key(server_hello) -> + ?LET(SelectedIdentity, selected_identity(), + #pre_shared_key_server_hello{ + selected_identity = SelectedIdentity}). + +selected_identity() -> + rand:uniform(32). + +offered_psks() -> + ?LET(Size, choose(1,5), + #offered_psks{ + identities = psk_identities(Size), + binders = psk_binders(Size)}). + +psk_identities(Size) -> + psk_identities(Size, []). +%% +psk_identities(0, Acc) -> + Acc; +psk_identities(N, Acc) -> + psk_identities(N - 1, [psk_identity()|Acc]). + +psk_identity() -> + Len = rand:uniform(32), + Identity = crypto:strong_rand_bytes(Len), + Age = crypto:strong_rand_bytes(4), + #psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}. + +psk_binders(Size) -> + psk_binders(Size, []). +%% +psk_binders(0, Acc) -> + Acc; +psk_binders(N, Acc) -> + psk_binders(N - 1, [psk_binder()|Acc]). + +psk_binder() -> + Len = rand:uniform(224) + 31, + crypto:strong_rand_bytes(Len). diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl new file mode 100644 index 0000000000..cc0b636580 --- /dev/null +++ b/lib/ssl/test/ssl_alert_SUITE.erl @@ -0,0 +1,100 @@ +%% +%% Copyright Ericsson AB 2019-2019. 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_alert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-include_lib("ssl/src/ssl_alert.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + alerts, + alert_details, + alert_details_not_too_big + ]. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +alerts() -> + [{doc, "Test ssl_alert:alert_txt/1"}]. +alerts(Config) when is_list(Config) -> + Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, + ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, + ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, + ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, + ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, + ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, + ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, + ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, + ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, + ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, + 255 %% Unsupported/unknow alert will result in a description too + ], + Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | + [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], + lists:foreach(fun(Alert) -> + try ssl_alert:alert_txt(Alert) + catch + C:E:T -> + ct:fail({unexpected, {C, E, T}}) + end + end, Alerts). +%%-------------------------------------------------------------------- +alert_details() -> + [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. +alert_details(Config) when is_list(Config) -> + Unique = make_ref(), + UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), + case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of + 0 -> + ct:fail(error_details_missing); + _ -> + ok + end. + +%%-------------------------------------------------------------------- +alert_details_not_too_big() -> + [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. +alert_details_not_too_big(Config) when is_list(Config) -> + Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), + case length(ssl_alert:alert_txt(Alert)) < 1000 of + true -> + ok; + false -> + ct:fail(ssl_alert_text_too_big) + end. diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl index dfc780479e..82a49e1469 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_alpn_handshake_SUITE). +-module(ssl_alpn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -32,7 +32,9 @@ %%-------------------------------------------------------------------- all() -> - [{group, 'tlsv1.2'}, + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'sslv3'}, @@ -42,12 +44,13 @@ all() -> groups() -> [ - {'tlsv1.2', [], alpn_tests()}, - {'tlsv1.1', [], alpn_tests()}, - {'tlsv1', [], alpn_tests()}, + {'tlsv1.3', [], alpn_tests() -- [client_renegotiate, session_reused]}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist()}, {'sslv3', [], alpn_not_supported()}, - {'dtlsv1.2', [], alpn_tests() -- [client_renegotiate]}, - {'dtlsv1', [], alpn_tests() -- [client_renegotiate]} + {'dtlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'dtlsv1', [], alpn_tests() ++ alpn_npn_coexist()} ]. alpn_tests() -> @@ -61,12 +64,16 @@ alpn_tests() -> client_alpn_and_server_no_support, client_no_support_and_server_alpn, client_alpn_npn_and_server_alpn, - client_alpn_npn_and_server_alpn_npn, - client_alpn_and_server_alpn_npn, client_renegotiate, session_reused ]. +alpn_npn_coexist() -> + [ + client_alpn_npn_and_server_alpn_npn, + client_alpn_and_server_alpn_npn + ]. + alpn_not_supported() -> [alpn_not_supported_client, alpn_not_supported_server @@ -77,8 +84,7 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl_test_lib:clean_start(), - Config = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl new file mode 100644 index 0000000000..fefecc0b65 --- /dev/null +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -0,0 +1,1976 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, + new_options_in_handshake]) + ++ (since_1_2() -- [conf_signature_algs])}, + {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()}, + {'sslv3', [], (gen_api_tests() -- [new_options_in_handshake]) ++ beast_mitigation_test() ++ pre_1_3()}, + {'dtlsv1.2', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()}, + {'dtlsv1', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()} + ]. + +since_1_2() -> + [ + conf_signature_algs, + no_common_signature_algs + ]. + +pre_1_3() -> + [ + default_reject_anonymous + ]. +gen_api_tests() -> + [ + peercert, + peercert_with_client_cert, + connection_information, + secret_connection_info, + versions, + active_n, + dh_params, + hibernate, + hibernate_right_away, + listen_socket, + recv_active, + recv_active_once, + recv_active_n, + recv_timeout, + recv_close, + controlling_process, + controller_dies, + controlling_process_transport_accept_socket, + close_with_timeout, + close_in_error_state, + call_in_error_state, + close_transport_accept, + abuse_transport_accept_socket, + honor_server_cipher_order, + honor_client_cipher_order, + ipv6, + der_input, + new_options_in_handshake, + max_handshake_size, + invalid_certfile, + invalid_cacertfile, + invalid_keyfile, + options_not_proplist, + invalid_options + ]. + +handshake_paus_tests() -> + [ + handshake_continue, + handshake_continue_timeout, + hello_client_cancel, + hello_server_cancel + ]. + +%% Only relevant for SSL 3.0 and TLS 1.1 +beast_mitigation_test() -> + [%% Original option + rizzo_disabled, + %% Same effect as disable + rizzo_zero_n, + %% Same as default + rizzo_one_n_minus_one + ]. + +tls13_group() -> + [ + supported_groups, + honor_server_cipher_order_tls13, + honor_client_cipher_order_tls13 + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(prf, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Version = ssl_test_lib:protocol_version(Config), + PRFS = [md5, sha, sha256, sha384, sha512], + %% All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) + %% with the specified PRF algorithm + ExpectedPrfResults = + [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, + {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, + {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, + {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, + {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, + %% TLS 1.0 and 1.1 PRF: + {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], + TestPlan = prf_create_plan([Version], PRFS, ExpectedPrfResults), + [{prf_test_plan, TestPlan} | Config]; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(internal_active_n, _Config) -> + application:unset_env(ssl, internal_active_n); +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +peercert() -> + [{doc,"Test API function peercert/1"}]. +peercert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + CertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), + + ServerMsg = {error, no_peercert}, + ClientMsg = {ok, BinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +peercert_with_client_cert() -> + [{doc,"Test API function peercert/1"}]. +peercert_with_client_cert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), + ClientCertFile = proplists:get_value(certfile, ClientOpts), + [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), + + ServerMsg = {ok, ClientBinCert}, + ClientMsg = {ok, ServerBinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +connection_information() -> + [{doc,"Test the API function ssl:connection_information/1"}]. +connection_information(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- + +secret_connection_info() -> + [{doc,"Test the API function ssl:connection_information/2"}]. +secret_connection_info(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} |ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, true, Client, true), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +prf() -> + [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. +prf(Config) when is_list(Config) -> + TestPlan = proplists:get_value(prf_test_plan, Config), + case TestPlan of + [] -> ct:fail({error, empty_prf_test_plan}); + _ -> lists:foreach(fun(Suite) -> + lists:foreach( + fun(Test) -> + V = proplists:get_value(tls_ver, Test), + C = proplists:get_value(ciphers, Test), + E = proplists:get_value(expected, Test), + P = proplists:get_value(prf, Test), + prf_run_test(Config, V, C, E, P) + end, Suite) + end, TestPlan) + end. + +%%-------------------------------------------------------------------- +dh_params() -> + [{doc,"Test to specify DH-params file in server."}]. + +dh_params(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{dhfile, DHParamFile} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +conf_signature_algs() -> + [{doc,"Test to set the signature_algs option on both client and server"}]. +conf_signature_algs(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +no_common_signature_algs() -> + [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. +no_common_signature_algs(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{signature_algs, [{sha256, rsa}]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{signature_algs, [{sha384, rsa}]} + | ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%-------------------------------------------------------------------- +handshake_continue() -> + [{doc, "Test API function ssl:handshake_continue/3"}]. +handshake_continue(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, + {verify, verify_peer}, + {handshake, hello} | ServerOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%------------------------------------------------------------------ +handshake_continue_timeout() -> + [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. +handshake_continue_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 1}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}, + {verify, verify_peer} | ServerOpts], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, {error,timeout}), + ssl_test_lib:close(Server). + + +%%-------------------------------------------------------------------- +hello_client_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. +hello_client_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts], Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), + + Port = ssl_test_lib:inet_port(Server), + + %% That is ssl:handshake_cancel returns ok + {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts], Config)}, + {continue_options, cancel}]), + ssl_test_lib:check_server_alert(Server, user_canceled). +%%-------------------------------------------------------------------- +hello_server_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. +hello_server_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts + ], Config)}, + {continue_options, cancel}]), + + Port = ssl_test_lib:inet_port(Server), + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok). + +%%-------------------------------------------------------------------- +versions() -> + [{doc,"Test API function versions/0"}]. + +versions(Config) when is_list(Config) -> + [_|_] = Versions = ssl:versions(), + ct:log("~p~n", [Versions]). + +%%-------------------------------------------------------------------- +%% Test case adapted from gen_tcp_misc_SUITE. +active_n() -> + [{doc,"Test {active,N} option"}]. + +active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + N = 3, + LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), + [{active,N}] = ok(ssl:getopts(LS, [active])), + active_n_common(LS, N), + Self = self(), + spawn_link(fun() -> + S0 = ok(ssl:transport_accept(LS)), + {ok, S} = ssl:handshake(S0), + ok = ssl:setopts(S, [{active,N}]), + [{active,N}] = ok(ssl:getopts(S, [active])), + ssl:controlling_process(S, Self), + Self ! {server, S} + end), + C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), + [{active,N}] = ok(ssl:getopts(C, [active])), + S = receive + {server, S0} -> S0 + after + 1000 -> + exit({error, connect}) + end, + active_n_common(C, N), + active_n_common(S, N), + ok = ssl:setopts(C, [{active,N}]), + ok = ssl:setopts(S, [{active,N}]), + ReceiveMsg = fun(Socket, Msg) -> + receive + {ssl,Socket,Msg} -> + ok; + {ssl,Socket,Begin} -> + receive + {ssl,Socket,End} -> + Msg = Begin ++ End, + ok + after 1000 -> + exit(timeout) + end + after 1000 -> + exit(timeout) + end + end, + repeat(3, fun(I) -> + Msg = "message "++integer_to_list(I), + ok = ssl:send(C, Msg), + ReceiveMsg(S, Msg), + ok = ssl:send(S, Msg), + ReceiveMsg(C, Msg) + end), + receive + {ssl_passive,S} -> + [{active,false}] = ok(ssl:getopts(S, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + receive + {ssl_passive,C} -> + [{active,false}] = ok(ssl:getopts(C, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + LS2 = ok(ssl:listen(0, [{active,0}])), + receive + {ssl_passive,LS2} -> + [{active,false}] = ok(ssl:getopts(LS2, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + ok = ssl:close(LS2), + ok = ssl:close(C), + ok = ssl:close(S), + ok = ssl:close(LS), + ok. + +hibernate() -> + [{doc,"Check that an SSL connection that is started with option " + "{hibernate_after, 1000} indeed hibernates after 1000ms of " + "inactivity"}]. + +hibernate(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{hibernate_after, 1000}|ClientOpts]}]), + {current_function, _} = + process_info(Pid, current_function), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ct:sleep(1500), + {current_function, {erlang, hibernate, 3}} = + process_info(Pid, current_function), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +hibernate_right_away() -> + [{doc,"Check that an SSL connection that is configured to hibernate " + "after 0 or 1 milliseconds hibernates as soon as possible and not " + "crashes"}]. + +hibernate_right_away(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + StartServerOpts = [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}], + StartClientOpts = [return_socket, + {node, ClientNode}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}], + + Server1 = ssl_test_lib:start_server(StartServerOpts), + Port1 = ssl_test_lib:inet_port(Server1), + {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid1, current_function), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + + Server2 = ssl_test_lib:start_server(StartServerOpts), + Port2 = ssl_test_lib:inet_port(Server2), + {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), + + ssl_test_lib:check_result(Server2, ok, Client2, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid2, current_function), + + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client2). + +listen_socket() -> + [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. + +listen_socket(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ok, ListenSocket} = ssl:listen(0, ServerOpts), + + %% This can be a valid thing to do as + %% options are inherited by the accept socket + ok = ssl:controlling_process(ListenSocket, self()), + + {ok, _} = ssl:sockname(ListenSocket), + + {error, enotconn} = ssl:send(ListenSocket, <<"data">>), + {error, enotconn} = ssl:recv(ListenSocket, 0), + {error, enotconn} = ssl:connection_information(ListenSocket), + {error, enotconn} = ssl:peername(ListenSocket), + {error, enotconn} = ssl:peercert(ListenSocket), + {error, enotconn} = ssl:renegotiate(ListenSocket), + {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), + {error, enotconn} = ssl:shutdown(ListenSocket, read_write), + + ok = ssl:close(ListenSocket). + +%%-------------------------------------------------------------------- +recv_active() -> + [{doc,"Test recv on active socket"}]. + +recv_active(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_once() -> + [{doc,"Test recv on active (once) socket"}]. + +recv_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_n() -> + [{doc,"Test recv on active (n) socket"}]. + +recv_active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +recv_timeout() -> + [{doc,"Test ssl:ssl_accept timeout"}]. + +recv_timeout(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_timeout_server, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_recv_result_timeout_client, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_close() -> + [{doc,"Special case of call error handling"}]. +recv_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, do_recv_close, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl:close(SslSocket), + ssl_test_lib:check_result(Server, ok). + + + +%%-------------------------------------------------------------------- +controlling_process() -> + [{doc,"Test API function controlling_process/2"}]. + +controlling_process(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Server hello", + ServerMsg = "Client hello", + + Server = ssl_test_lib:start_server([ + {node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, CSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), + %% We do not have the TLS server socket but all messages form the client + %% socket are now read, so ramining are form the server socket + ClientMsg = ssl_active_recv(length(ClientMsg)), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +controller_dies() -> + [{doc,"Test that the socket is closed after controlling process dies"}]. +controller_dies(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Hello server", + ServerMsg = "Hello client", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), + ct:sleep(?SLEEP), %% so that they are connected + + process_flag(trap_exit, true), + + %% Test that clients die + exit(Client, killed), + get_close(Client, ?LINE), + + %% Test that clients die when process disappear + Server ! listen, + Tester = self(), + Connect = fun(Pid) -> + {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), + %% Make sure server finishes and verification + %% and is in coonection state before + %% killing client + ct:sleep(?SLEEP), + Pid ! {self(), connected, Socket}, + receive die_nice -> normal end + end, + Client2 = spawn_link(fun() -> Connect(Tester) end), + receive {Client2, connected, _Socket} -> Client2 ! die_nice end, + + get_close(Client2, ?LINE), + + %% Test that clients die when the controlling process have changed + Server ! listen, + + Client3 = spawn_link(fun() -> Connect(Tester) end), + Controller = spawn_link(fun() -> receive die_nice -> normal end end), + receive + {Client3, connected, Socket} -> + ok = ssl:controlling_process(Socket, Controller), + Client3 ! die_nice + end, + + ct:log("Wating on exit ~p~n",[Client3]), + receive {'EXIT', Client3, normal} -> ok end, + + receive %% Client3 is dead but that doesn't matter, socket should not be closed. + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 1000 -> + ok + end, + Controller ! die_nice, + get_close(Controller, ?LINE), + + %% Test that servers die + Server ! listen, + LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + ct:sleep(?SLEEP), %% so that they are connected + + exit(Server, killed), + get_close(Server, ?LINE), + process_flag(trap_exit, false), + ssl_test_lib:close(LastClient). +%%-------------------------------------------------------------------- +controlling_process_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +controlling_process_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server). + +%%-------------------------------------------------------------------- +close_with_timeout() -> + [{doc,"Test normal (not downgrade) ssl:close/2"}]. +close_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options,[{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%%-------------------------------------------------------------------- +close_in_error_state() -> + [{doc,"Special case of closing socket in error state"}]. +close_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), + receive + {_Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + ok -> + ok; + Other -> + ct:fail(Other) + end. + +%%-------------------------------------------------------------------- +call_in_error_state() -> + [{doc,"Special case of call error handling"}]. +call_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), + receive + {Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. +%%-------------------------------------------------------------------- +close_transport_accept() -> + [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. + +close_transport_accept(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Port = 0, + Opts = [{active, false} | ServerOpts], + {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), + spawn_link(fun() -> + ct:sleep(?SLEEP), + rpc:call(ServerNode, ssl, close, [ListenSocket]) + end), + case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of + {error, closed} -> + ok; + Other -> + exit({?LINE, Other}) + end. +%%-------------------------------------------------------------------- +abuse_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +abuse_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +invalid_keyfile() -> + [{doc,"Test what happens with an invalid key file"}]. +invalid_keyfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), + "badkey.pem"]), + BadOpts = [{keyfile, BadKeyFile}| proplists:delete(keyfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, BadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + File = proplists:get_value(keyfile,BadOpts), + ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, + {error, closed}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_server_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +honor_client_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_client_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +ipv6() -> + [{require, ipv6_hosts}, + {doc,"Test ipv6."}]. +ipv6(Config) when is_list(Config) -> + {ok, Hostname0} = inet:gethostname(), + + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = + ssl_test_lib:run_where(Config, ipv6), + Server = ssl_test_lib:start_server([{node, ServerNode}, + {port, 0}, {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client); + false -> + {skip, "Host does not support IPv6"} + end. + +%%-------------------------------------------------------------------- +der_input() -> + [{doc,"Test to input certs and key as der"}]. + +der_input(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [CADb | _] = element(6, State), + + Size = ets:info(CADb, size), + + SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + SeverVerifyOpts]), + ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + ClientVerifyOpts]), + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], + ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + Size = ets:info(CADb, size). + +%%-------------------------------------------------------------------- +invalid_certfile() -> + [{doc,"Test what happens with an invalid cert file"}]. + +invalid_certfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcert.pem"]), + ServerBadOpts = [{certfile, BadCertFile}| proplists:delete(certfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerBadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + File = proplists:get_value(certfile, ServerBadOpts), + ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, + Client, {error, closed}). + + +%%-------------------------------------------------------------------- +invalid_cacertfile() -> + [{doc,"Test what happens with an invalid cacert file"}]. + +invalid_cacertfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCACertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcacert.pem"]), + ServerBadOpts = [{cacertfile, BadCACertFile}| proplists:delete(cacertfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts}]), + + Port0 = ssl_test_lib:inet_port(Server0), + + + Client0 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + File0 = proplists:get_value(cacertfile, ServerBadOpts), + + ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, + Client0, {error, closed}), + + File = File0 ++ "do_not_exit.pem", + ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], + + Server1 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts1}]), + + Port1 = ssl_test_lib:inet_port(Server1), + + Client1 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + + ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, + Client1, {error, closed}), + ok. + +%%-------------------------------------------------------------------- +new_options_in_handshake() -> + [{doc,"Test that you can set ssl options in handshake/3 and not only in tcp upgrade"}]. +new_options_in_handshake(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + [_, Cipher | _] = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version), + [{key_exchange, + fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (ecdh_rsa) -> + true; + (rsa) -> + true; + (_) -> + false + end + }]), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {ssl_extra_opts, [{versions, [Version]}, + {ciphers,[Cipher]}]}, %% To be set in ssl_accept/3 + {mfa, {?MODULE, connection_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, [Cipher]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = {ok, {Version, Cipher}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%------------------------------------------------------------------- +max_handshake_size() -> + [{doc,"Test that we can set max_handshake_size to max value."}]. + +max_handshake_size(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{max_handshake_size, 8388607} |ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{max_handshake_size, 8388607} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +%%------------------------------------------------------------------- +options_not_proplist() -> + [{doc,"Test what happens if an option is not a key value tuple"}]. + +options_not_proplist(Config) when is_list(Config) -> + BadOption = {client_preferred_next_protocols, + client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, + {option_not_a_key_value_tuple, BadOption} = + ssl:connect("twitter.com", 443, [binary, {active, false}, + BadOption]). + +%%------------------------------------------------------------------- +invalid_options() -> + [{doc,"Test what happens when we give invalid options"}]. + +invalid_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> + ssl_test_lib:check_result(Server, + {error, {options, {sslv2, Option}}}, + Client, + {error, {options, {sslv2, Option}}}); + (Client, Server, Option) -> + ssl_test_lib:check_result(Server, + {error, {options, Option}}, + Client, + {error, {options, Option}}) + end, + + TestOpts = + [{versions, [sslv2, sslv3]}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], + + [begin + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [TestOpt | ServerOpts]}]), + %% Will never reach a point where port is used. + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {host, Hostname}, {from, self()}, + {options, [TestOpt | ClientOpts]}]), + Check(Client, Server, TestOpt), + ok + end || TestOpt <- TestOpts], + ok. +%%------------------------------------------------------------------- + +default_reject_anonymous()-> + [{doc,"Test that by default anonymous cipher suites are rejected "}]. +default_reject_anonymous(Config) when is_list(Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + TLSVersion = ssl_test_lib:tls_version(Version), + + [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, + [{ciphers,[CipherSuite]} | + ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%------------------------------------------------------------------- +%% Note that these test only test that the options are valid to set. As application data +%% is a stream you can not test that the send acctually splits it up as when it arrives +%% again at the user layer it may be concatenated. But COVER can show that the split up +%% code has been run. + +rizzo_disabled() -> + [{doc, "Test original beast mitigation disable option for SSL 3.0 and TLS 1.0"}]. + +rizzo_disabled(Config) -> + ClientOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_zero_n() -> + [{doc, "Test zero_n beast mitigation option (same affect as original disable option) for SSL 3.0 and TLS 1.0"}]. + +rizzo_zero_n(Config) -> + ClientOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_one_n_minus_one () -> + [{doc, "Test beast_mitigation option one_n_minus_one (same affect as default) for SSL 3.0 and TLS 1.0"}]. + +rizzo_one_n_minus_one (Config) -> + ClientOpts = [{beast_mitigation, one_n_minus_one } | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, one_n_minus_one} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +supported_groups() -> + [{doc,"Test the supported_groups option in TLS 1.3."}]. + +supported_groups(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups, [x448, x25519]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups,[x448]} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +honor_client_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_client_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_server_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +peercert_result(Socket) -> + ssl:peercert(Socket). + +connection_information_result(Socket) -> + {ok, Info = [_ | _]} = ssl:connection_information(Socket), + case length(Info) > 3 of + true -> + %% Atleast one ssl_option() is set + ct:log("Info ~p", [Info]), + ok; + false -> + ct:fail(no_ssl_options_returned) + end. +secret_connection_info_result(Socket) -> + {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), + {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), + check_connection_info(Protocol, ConnInfo). + + +%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple. +%% client_random and server_random are not used in the TLS 1.3 key schedule. +check_connection_info('tlsv1.3', [{client_random, ClientRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ClientRand) andalso is_binary(MasterSecret); +check_connection_info('tlsv1.3', [{server_random, ServerRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]) -> + is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, _) -> + false. + + +prf_create_plan(TlsVersions, PRFs, Results) -> + lists:foldl(fun(Ver, Acc) -> + A = prf_ciphers_and_expected(Ver, PRFs, Results), + [A|Acc] + end, [], TlsVersions). + +prf_ciphers_and_expected(TlsVer, PRFs, Results) -> + case TlsVer of + TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 + orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> + Ciphers = ssl:cipher_suites(), + {_, Expected} = lists:keyfind(md5sha, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; + TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> + lists:foldl( + fun(PRF, Acc) -> + Ciphers = prf_get_ciphers(TlsVer, PRF), + case Ciphers of + [] -> + ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), + Acc; + Ciphers -> + {_, Expected} = lists:keyfind(PRF, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, + {prf, PRF}] | Acc] + end + end, [], PRFs) + end. + +prf_get_ciphers(_, PRF) -> + lists:filter( + fun(C) when tuple_size(C) == 4 andalso + element(4, C) == PRF -> + true; + (_) -> + false + end, + ssl:cipher_suites()). + +prf_run_test(_, TlsVer, [], _, Prf) -> + ct:fail({error, cipher_list_empty, TlsVer, Prf}); +prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], + ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), + ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), + Server = ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +prf_verify_value(Socket, TlsVer, Expected, Algo) -> + Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), + case TlsVer of + sslv3 -> + case Ret of + {error, undefined} -> ok; + _ -> + {error, {expected, {error, undefined}, + got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} + end; + _ -> + case Ret of + {ok, Expected} -> ok; + {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, + prf_algorithm, Algo}} + end + end. + +tls_or_dtls('dtlsv1') -> + dtls; +tls_or_dtls('dtlsv1.2') -> + dtls; +tls_or_dtls(_) -> + tls. + +active_n_common(S, N) -> + ok = ssl:setopts(S, [{active,-N}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,0}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + ok = ssl:setopts(S, [{active,32767}]), + {error,{options,_}} = ssl:setopts(S, [{active,1}]), + {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), + ok = ssl:setopts(S, [{active,-32768}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,true}]), + [{active,true}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,once}]), + [{active,once}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + {error,{options,_}} = ssl:setopts(S, [{active,32768}]), + ok = ssl:setopts(S, [{active,false}]), + [{active,false}] = ok(ssl:getopts(S, [active])), + ok. + +ok({ok,V}) -> V. + +repeat(N, Fun) -> + repeat(N, N, Fun). + +repeat(N, T, Fun) when is_integer(N), N > 0 -> + Fun(T-N), + repeat(N-1, T, Fun); +repeat(_, _, _) -> + ok. + +try_recv_active(Socket) -> + ssl:send(Socket, "Hello world"), + {error, einval} = ssl:recv(Socket, 11), + ok. +try_recv_active_once(Socket) -> + {error, einval} = ssl:recv(Socket, 11), + ok. + +controlling_process_result(Socket, Pid, Msg) -> + ok = ssl:controlling_process(Socket, Pid), + %% Make sure other side has evaluated controlling_process + %% before message is sent + ct:sleep(?SLEEP), + ssl:send(Socket, Msg), + no_result_msg. + +controller_dies_result(_Socket, _Pid, _Msg) -> + receive Result -> Result end. +get_close(Pid, Where) -> + receive + {'EXIT', Pid, _Reason} -> + receive + {_, {ssl_closed, Socket}} -> + ct:log("Socket closed ~p~n",[Socket]); + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end; + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end. + +ssl_active_recv(N) -> + ssl_active_recv(N, []). + +ssl_active_recv(0, Acc) -> + Acc; +ssl_active_recv(N, Acc) -> + receive + {ssl, _, Bytes} -> + ssl_active_recv(N-length(Bytes), Acc ++ Bytes) + end. + +send_recv_result_timeout_client(Socket) -> + {error, timeout} = ssl:recv(Socket, 11, 500), + {error, timeout} = ssl:recv(Socket, 11, 0), + ssl:send(Socket, "Hello world"), + receive + Msg -> + io:format("Msg ~p~n",[Msg]) + after 500 -> + ok + end, + {ok, "Hello world"} = ssl:recv(Socket, 11, 500), + ok. +send_recv_result_timeout_server(Socket) -> + ssl:send(Socket, "Hello"), + {ok, "Hello world"} = ssl:recv(Socket, 11), + ssl:send(Socket, " world"), + ok. + +do_recv_close(Socket) -> + {error, closed} = ssl:recv(Socket, 11), + receive + {_,{error,closed}} -> + error_extra_close_sent_to_user_process + after 500 -> + ok + end. + +tls_close(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + case ssl:close(Socket, 5000) of + ok -> + ok; + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. + +run_error_server_close([Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:close(Socket). + +run_error_server([ Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:controlling_process(Socket, self()). + +run_client_error([Port, Opts]) -> + ssl:connect("localhost", Port, Opts). + +honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} + | ClientOpts]}]), + + Version = ssl_test_lib:protocol_version(Config), + + ServerMsg = ClientMsg = {ok, {Version, Expected}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +connection_info_result(Socket) -> + {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), + {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. + +der_input_opts(Opts) -> + Certfile = proplists:get_value(certfile, Opts), + CaCertsfile = proplists:get_value(cacertfile, Opts), + Keyfile = proplists:get_value(keyfile, Opts), + Dhfile = proplists:get_value(dhfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), + [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), + [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), + CaCerts = + lists:map(fun(Entry) -> + {_, CaCert, _} = Entry, + CaCert + end, ssl_test_lib:pem_to_der(CaCertsfile)), + {Cert, {Asn1Type, Key}, CaCerts, DHParams}. diff --git a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem index feb581da30..feb581da30 100644 --- a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem +++ b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl new file mode 100644 index 0000000000..27fbcb8e47 --- /dev/null +++ b/lib/ssl/test/ssl_app_env_SUITE.erl @@ -0,0 +1,171 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_app_env_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tests()}, + {'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], tests()}, + {'dtlsv1', [], tests()} + ]. + +tests() -> + [ + internal_active_1, + protocol_versions, + empty_protocol_versions + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(internal_active_1, Config) -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, internal_active_n, 1), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(protocol_versions, Config) -> + Version = ssl_test_lib:protocol_version(Config), + case atom_to_list(Version) of + "d" ++ _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, dtls_protocol_version, [Version]), + ssl:start(); + _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, protocol_version, [Version]), + ssl:start() + end, + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(empty_protocol_versions, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, protocol_version, []), + application:set_env(ssl, dtls_protocol_version, []), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_, _Config) -> + ssl_test_lib:clean_start(). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +internal_active_1() -> + [{doc,"Test internal active 1 (behave as internal active once)"}]. + +internal_active_1(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +protocol_versions() -> + [{doc,"Test to set a list of protocol versions in app environment."}]. + +protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +empty_protocol_versions() -> + [{doc,"Test to set an empty list of protocol versions in app environment."}]. + +empty_protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 86a0aaf67b..355cd31070 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -26,15 +26,8 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). --include_lib("public_key/include/public_key.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). --include("ssl_api.hrl"). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_alert.hrl"). --include("ssl_internal.hrl"). --include("tls_record.hrl"). --include("tls_handshake.hrl"). -define(TIMEOUT, 20000). -define(EXPIRE, 10). @@ -49,243 +42,35 @@ all() -> [ {group, basic}, - {group, basic_tls}, - {group, options}, - {group, options_tls}, - {group, session}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}, - {group, 'tlsv1.3'}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'} + {group, options} ]. groups() -> [{basic, [], basic_tests()}, - {basic_tls, [], basic_tests_tls()}, - {options, [], options_tests()}, - {options_tls, [], options_tests_tls()}, - {'dtlsv1.2', [], all_versions_groups()}, - {'dtlsv1', [], all_versions_groups()}, - {'tlsv1.3', [], tls13_test_group()}, - {'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]}, - {'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()}, - {'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()}, - {'sslv3', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests() ++ [tls_ciphersuite_vs_version]}, - {api,[], api_tests()}, - {api_tls,[], api_tests_tls()}, - {session, [], session_tests()}, - {renegotiate, [], renegotiate_tests()}, - {ciphers, [], cipher_tests()}, - {error_handling_tests, [], error_handling_tests()}, - {error_handling_tests_tls, [], error_handling_tests_tls()} + {options, [], options_tests()} ]. -tls_versions_groups ()-> - [ - {group, api_tls}, - {group, error_handling_tests_tls}]. - -all_versions_groups ()-> - [{group, api}, - {group, renegotiate}, - {group, ciphers}, - {group, error_handling_tests}]. - - basic_tests() -> [app, - appup, - alerts, - alert_details, - alert_details_not_too_big, + appup, version_option, connect_twice, connect_dist, - clear_pem_cache, defaults, fallback, cipher_format, - suite_to_str - ]. - -basic_tests_tls() -> - [tls_send_close - ]. - -options_tests() -> - [der_input, - ssl_options_not_proplist, - raw_ssl_option, - invalid_inet_get_option, - invalid_inet_get_option_not_list, - invalid_inet_get_option_improper_list, - invalid_inet_set_option, - invalid_inet_set_option_not_list, - invalid_inet_set_option_improper_list, - dh_params, - invalid_certfile, - invalid_cacertfile, - invalid_keyfile, - invalid_options, - protocol_versions, - empty_protocol_versions, - ipv6, - reuseaddr, - honor_server_cipher_order, - honor_client_cipher_order, - unordered_protocol_versions_server, - unordered_protocol_versions_client, - max_handshake_size -]. - -options_tests_tls() -> - [tls_misc_ssl_options, - tls_tcp_reuseaddr]. - -api_tests() -> - [secret_connection_info, - connection_information, - peercert, - peercert_with_client_cert, - versions, + tls_versions_option, eccs, - controlling_process, - getstat, - close_with_timeout, - hibernate, - hibernate_right_away, - listen_socket, - ssl_recv_timeout, - server_name_indication_option, - accept_pool, - prf, - socket_options, - active_n, - internal_active_1, cipher_suites, - handshake_continue, - handshake_continue_timeout, - hello_client_cancel, - hello_server_cancel - ]. - -api_tests_tls() -> - [tls_versions_option, - tls_upgrade, - tls_upgrade_with_timeout, - tls_ssl_accept_timeout, - tls_downgrade, - tls_shutdown, - tls_shutdown_write, - tls_shutdown_both, - tls_shutdown_error, - peername, - sockname, - tls_socket_options, - new_options_in_accept - ]. - -session_tests() -> - [reuse_session, - reuse_session_expired, - server_does_not_want_to_reuse_session, - no_reuses_session_server_restart_new_cert, - no_reuses_session_server_restart_new_cert_file]. - -renegotiate_tests() -> - [client_renegotiate, - server_renegotiate, - client_secure_renegotiate, - client_secure_renegotiate_fallback, - client_renegotiate_reused_session, - server_renegotiate_reused_session, - client_no_wrap_sequence_number, - server_no_wrap_sequence_number, - renegotiate_dos_mitigate_active, - renegotiate_dos_mitigate_passive, - renegotiate_dos_mitigate_absolute]. - -cipher_tests() -> - [old_cipher_suites, - cipher_suites_mix, - default_reject_anonymous]. - -error_handling_tests()-> - [close_transport_accept, - recv_active, - recv_active_once, - recv_active_n, - recv_error_handling, - call_in_error_state, - close_in_error_state, - abuse_transport_accept_socket, - controlling_process_transport_accept_socket - ]. - -error_handling_tests_tls()-> - [controller_dies, - tls_client_closes_socket, - tls_closed_in_active_once, - tls_tcp_error_propagation_in_active_mode, - tls_tcp_connect, - tls_tcp_connect_big, - tls_dont_crash_on_handshake_garbage + old_cipher_suites, + cipher_suites_mix ]. -rizzo_tests() -> - [rizzo, - no_rizzo_rc4, - rizzo_one_n_minus_one, - rizzo_zero_n, - rizzo_disabled]. - -%% For testing TLS 1.3 features and possible regressions -tls13_test_group() -> - [handshake_continue_tls13_client, - tls13_enable_client_side, - tls13_enable_server_side, - tls_record_1_3_encode_decode, - tls13_finished_verify_data, - tls13_1_RTT_handshake, - tls12_ssl_server_tls13_ssl_client, - tls13_basic_ssl_server_openssl_client, - tls13_basic_ssl_server_ssl_client, - tls13_basic_openssl_server_ssl_client, - tls13_custom_groups_ssl_server_openssl_client, - tls13_custom_groups_ssl_server_ssl_client, - tls13_hello_retry_request_ssl_server_openssl_client, - tls13_hello_retry_request_ssl_server_ssl_client, - tls13_client_auth_empty_cert_alert_ssl_server_openssl_client, - tls13_client_auth_empty_cert_alert_ssl_server_ssl_client, - tls13_client_auth_empty_cert_ssl_server_openssl_client, - tls13_client_auth_empty_cert_ssl_server_ssl_client, - tls13_client_auth_ssl_server_openssl_client, - tls13_client_auth_ssl_server_ssl_client, - tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client, - tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client, - tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client, - tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client, - tls13_hrr_client_auth_ssl_server_openssl_client, - tls13_hrr_client_auth_ssl_server_ssl_client, - tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client, - tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client, - tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client, - tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client, - tls13_connection_information, - tls13_ssl_server_with_alpn_ssl_client, - tls13_ssl_server_with_alpn_ssl_client_empty_alpn, - tls13_ssl_server_with_alpn_ssl_client_bad_alpn, - tls13_ssl_server_with_alpn_ssl_client_alpn, - tls13_ecdsa_ssl_server_openssl_client, - tls13_ecdsa_ssl_server_ssl_client, - tls13_ecdsa_openssl_server_ssl_client, - tls13_ecdsa_client_auth_ssl_server_ssl_client - ]. +options_tests() -> + [ + unordered_protocol_versions_server, + unordered_protocol_versions_client]. -%%-------------------------------------------------------------------- init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of @@ -308,229 +93,6 @@ end_per_suite(_Config) -> application:stop(crypto). %%-------------------------------------------------------------------- - -init_per_group(GroupName, Config) when GroupName == basic_tls; - GroupName == options_tls; - GroupName == options; - GroupName == basic; - GroupName == session; - GroupName == error_handling_tests_tls -> - ssl_test_lib:clean_tls_version(Config); -%% Do not automatically configure TLS version for the 'tlsv1.3' group -init_per_group('tlsv1.3' = GroupName, Config) -> - case ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl:start(), - Config; - false -> - {skip, "Missing crypto support"} - end; -init_per_group(GroupName, Config) -> - ssl_test_lib:clean_tls_version(Config), - case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - _ -> - case ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl:start(), - Config; - false -> - {skip, "Missing crypto support"} - end - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -%%-------------------------------------------------------------------- -init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client; - Case == unordered_protocol_versions_server-> - case proplists:get_value(supported, ssl:versions()) of - ['tlsv1.2' | _] -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "TLS 1.2 need but not supported on this platform"} - end; - -init_per_testcase(protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - %% For backwards compatibility sslv2 should be filtered out. - application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(reuse_session_expired, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, session_lifetime, ?EXPIRE), - application:set_env(ssl, session_delay_cleanup_time, 500), - ssl:start(), - ct:timetrap({seconds, 30}), - Config; - -init_per_testcase(empty_protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, protocol_version, []), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(fallback, Config) -> - case tls_record:highest_protocol_version([]) of - {3, N} when N > 1 -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "Not relevant if highest supported version is less than 3.2"} - end; - -init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; - TestCase == server_renegotiate; - TestCase == client_secure_renegotiate; - TestCase == client_renegotiate_reused_session; - TestCase == server_renegotiate_reused_session; - TestCase == client_no_wrap_sequence_number; - TestCase == server_no_wrap_sequence_number; - TestCase == renegotiate_dos_mitigate_active; - TestCase == renegotiate_dos_mitigate_passive; - TestCase == renegotiate_dos_mitigate_absolute -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, ?SEC_RENEGOTIATION_TIMEOUT + 5}), - Config; - -init_per_testcase(TestCase, Config) when TestCase == versions_option; - TestCase == tls_tcp_connect_big -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(version_option, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(reuse_session, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(rizzo, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(no_rizzo_rc4, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(rizzo_one_n_minus_one, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(one_n_minus_one, Config); - -init_per_testcase(rizzo_zero_n, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(zero_n, Config); - -init_per_testcase(rizzo_disabled, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(disabled, Config); - -init_per_testcase(TestCase, Config) when TestCase == no_reuses_session_server_restart_new_cert_file; - TestCase == no_reuses_session_server_restart_new_cert -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 15}), - Config; - -init_per_testcase(prf, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), - case proplists:get_value(tc_group_path, Config) of - [] -> Prop = []; - [Prop] -> Prop - end, - case proplists:get_value(name, Prop) of - undefined -> TlsVersions = [sslv3, tlsv1, 'tlsv1.1', 'tlsv1.2']; - TlsVersion when is_atom(TlsVersion) -> - TlsVersions = [TlsVersion] - end, - PRFS=[md5, sha, sha256, sha384, sha512], - %All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) - %with the specified PRF algorithm - ExpectedPrfResults= - [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, - {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, - {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, - {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, - {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, - %TLS 1.0 and 1.1 PRF: - {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], - TestPlan = prf_create_plan(TlsVersions, PRFS, ExpectedPrfResults), - [{prf_test_plan, TestPlan} | Config]; - -init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; - TestCase == tls_client_closes_socket; - TestCase == tls_closed_in_active_once; - TestCase == tls_downgrade -> - ssl:stop(), - ssl:start(), - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 15}), - Config; -init_per_testcase(TestCase, Config) when TestCase == clear_pem_cache; - TestCase == der_input; - TestCase == defaults -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - %% White box test need clean start - ssl:stop(), - ssl:start(), - ct:timetrap({seconds, 20}), - Config; -init_per_testcase(raw_ssl_option, Config) -> - ct:timetrap({seconds, 5}), - case os:type() of - {unix,linux} -> - Config; - _ -> - {skip, "Raw options are platform-specific"} - end; - -init_per_testcase(accept_pool, Config) -> - ct:timetrap({seconds, 5}), - case proplists:get_value(protocol, Config) of - dtls -> - {skip, "Not yet supported on DTLS sockets"}; - _ -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - Config - end; - -init_per_testcase(internal_active_1, Config) -> - ssl:stop(), - application:load(ssl), - application:set_env(ssl, internal_active_n, 1), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(controller_dies, Config) -> - ct:timetrap({seconds, 10}), - Config; init_per_testcase(eccs, Config) -> case ssl:eccs() of [] -> @@ -545,23 +107,8 @@ init_per_testcase(_TestCase, Config) -> ct:timetrap({seconds, 5}), Config. -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - application:unset_env(ssl, session_delay_cleanup_time), - end_per_testcase(default_action, Config); - -end_per_testcase(internal_active_n, Config) -> - application:unset_env(ssl, internal_active_n), - end_per_testcase(default_action, Config); - -end_per_testcase(Case, Config) when Case == protocol_versions; - Case == empty_protocol_versions-> - application:unset_env(ssl, protocol_versions), - end_per_testcase(default_action, Config); - end_per_testcase(_TestCase, Config) -> Config. - %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -575,669 +122,76 @@ appup() -> appup(Config) when is_list(Config) -> ok = ?t:appup_test(ssl). %%-------------------------------------------------------------------- -alerts() -> - [{doc, "Test ssl_alert:alert_txt/1"}]. -alerts(Config) when is_list(Config) -> - Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, - ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, - ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, - ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, - ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, - ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, - ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, - ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, - ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, - ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, - 255 %% Unsupported/unknow alert will result in a description too - ], - Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | - [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], - lists:foreach(fun(Alert) -> - try ssl_alert:alert_txt(Alert) - catch - C:E:T -> - ct:fail({unexpected, {C, E, T}}) - end - end, Alerts). -%%-------------------------------------------------------------------- -alert_details() -> - [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. -alert_details(Config) when is_list(Config) -> - Unique = make_ref(), - UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), - case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of - 0 -> - ct:fail(error_details_missing); - _ -> - ok - end. - -%%-------------------------------------------------------------------- -alert_details_not_too_big() -> - [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. -alert_details_not_too_big(Config) when is_list(Config) -> - Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), - case length(ssl_alert:alert_txt(Alert)) < 1000 of - true -> - ok; - false -> - ct:fail(ssl_alert_text_too_big) - end. - -%%-------------------------------------------------------------------- -new_options_in_accept() -> - [{doc,"Test that you can set ssl options in ssl_accept/3 and not only in tcp upgrade"}]. -new_options_in_accept(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_dsa_opts, Config), - [_ , _ | ServerSslOpts] = ssl_test_lib:ssl_options(server_opts, Config), %% Remove non ssl opts - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Version = ssl_test_lib:protocol_options(Config, [{tls, sslv3}, {dtls, dtlsv1}]), - Cipher = ssl_test_lib:protocol_options(Config, [{tls, #{key_exchange =>rsa, - cipher => rc4_128, - mac => sha, - prf => default_prf - }}, - {dtls, #{key_exchange =>rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf - }}]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {ssl_extra_opts, [{versions, [Version]}, - {ciphers,[Cipher]} | ServerSslOpts]}, %% To be set in ssl_accept/3 - {mfa, {?MODULE, connection_info_result, []}}, - {options, proplists:delete(cacertfile, ServerOpts0)}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{versions, [Version]}, - {ciphers,[Cipher]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = {ok, {Version, Cipher}}, +version_option() -> + [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. +version_option(Config) when is_list(Config) -> + Versions = proplists:get_value(supported, ssl:versions()), + [version_option_test(Config, Version) || Version <- Versions]. - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -handshake_continue() -> - [{doc, "Test API function ssl:handshake_continue/3"}]. -handshake_continue(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -handshake_continue_tls13_client() -> - [{doc, "Test API function ssl:handshake_continue/3"}]. -handshake_continue_tls13_client(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - ClientOptsHello0 = ssl_test_lib:ssl_options([{handshake, hello}], Config), - ClientOptsHello = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOptsHello0], - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOptsHello}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%------------------------------------------------------------------ -handshake_continue_timeout() -> - [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. -handshake_continue_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 1}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, {error,timeout}), - ssl_test_lib:close(Server). - - -%%-------------------------------------------------------------------- -hello_client_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. -hello_client_cancel(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), - - Port = ssl_test_lib:inet_port(Server), - - %% That is ssl:handshake_cancel returns ok - {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - ssl_test_lib:check_server_alert(Server, user_canceled). %%-------------------------------------------------------------------- -hello_server_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. -hello_server_cancel(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - - Port = ssl_test_lib:inet_port(Server), - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok). - -%%-------------------------------------------------------------------- -prf() -> - [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. -prf(Config) when is_list(Config) -> - TestPlan = proplists:get_value(prf_test_plan, Config), - case TestPlan of - [] -> ct:fail({error, empty_prf_test_plan}); - _ -> lists:foreach(fun(Suite) -> - lists:foreach( - fun(Test) -> - V = proplists:get_value(tls_ver, Test), - C = proplists:get_value(ciphers, Test), - E = proplists:get_value(expected, Test), - P = proplists:get_value(prf, Test), - prf_run_test(Config, V, C, E, P) - end, Suite) - end, TestPlan) - end. - -%%-------------------------------------------------------------------- - -secret_connection_info() -> - [{doc,"Test the API function ssl:connection_information/2"}]. -secret_connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, secret_connection_info_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, secret_connection_info_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, true, Client, true), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- - -connection_information() -> - [{doc,"Test the API function ssl:connection_information/1"}]. -connection_information(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = ok, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -protocol_versions() -> - [{doc,"Test to set a list of protocol versions in app environment."}]. - -protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- -empty_protocol_versions() -> - [{doc,"Test to set an empty list of protocol versions in app environment."}]. - -empty_protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- - -controlling_process() -> - [{doc,"Test API function controlling_process/2"}]. - -controlling_process(Config) when is_list(Config) -> +connect_twice() -> + [{doc,""}]. +connect_twice(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Server hello", - ServerMsg = "Client hello", - - Server = ssl_test_lib:start_server([ - {node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, CSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), - %% We do not have the TLS server socket but all messages form the client - %% socket are now read, so ramining are form the server socket - ClientMsg = ssl_active_recv(length(ClientMsg)), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -getstat() -> - [{doc,"Test API function getstat/2"}]. -getstat(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port1 = ssl_test_lib:inet_port(Server1), - Server2 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port2 = ssl_test_lib:inet_port(Server2), - {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port1,[{active, once}|ClientOpts]]), - {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port2,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", - [self(), self(), Server1, Server2]), - - %% We only check that the values are non-zero initially - %% (due to the handshake), and that sending more changes the values. - - %% Passive socket. - - {ok, InitialStats} = ssl:getstat(PassiveC), - ct:pal("InitialStats ~p~n", [InitialStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - - ok = ssl:send(PassiveC, "Hello world"), - wait_for_send(PassiveC), - {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), - ct:pal("SStats ~p~n", [SStats]), - [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) - || Name <- [send_cnt, send_oct]]), - - %% Active socket. - - {ok, InitialAStats} = ssl:getstat(ActiveC), - ct:pal("InitialAStats ~p~n", [InitialAStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - _ = receive - {ssl, ActiveC, _} -> - ok - after - ?SLEEP -> - exit(timeout) - end, - - ok = ssl:send(ActiveC, "Hello world"), - wait_for_send(ActiveC), - {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), - ct:pal("ASStats ~p~n", [ASStats]), - [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) - || Name <- [send_cnt, send_oct]]), - - ok. - -%%-------------------------------------------------------------------- -controller_dies() -> - [{doc,"Test that the socket is closed after controlling process dies"}]. -controller_dies(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Hello server", - ServerMsg = "Hello client", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - ct:sleep(?SLEEP), %% so that they are connected - - process_flag(trap_exit, true), - - %% Test that clients die - exit(Client, killed), - get_close(Client, ?LINE), - - %% Test that clients die when process disappear - Server ! listen, - Tester = self(), - Connect = fun(Pid) -> - {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), - %% Make sure server finishes and verification - %% and is in coonection state before - %% killing client - ct:sleep(?SLEEP), - Pid ! {self(), connected, Socket}, - receive die_nice -> normal end - end, - Client2 = spawn_link(fun() -> Connect(Tester) end), - receive {Client2, connected, _Socket} -> Client2 ! die_nice end, - - get_close(Client2, ?LINE), - - %% Test that clients die when the controlling process have changed - Server ! listen, - - Client3 = spawn_link(fun() -> Connect(Tester) end), - Controller = spawn_link(fun() -> receive die_nice -> normal end end), - receive - {Client3, connected, Socket} -> - ok = ssl:controlling_process(Socket, Controller), - Client3 ! die_nice - end, - - ct:log("Wating on exit ~p~n",[Client3]), - receive {'EXIT', Client3, normal} -> ok end, - - receive %% Client3 is dead but that doesn't matter, socket should not be closed. - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 1000 -> - ok - end, - Controller ! die_nice, - get_close(Controller, ?LINE), - - %% Test that servers die - Server ! listen, - LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - ct:sleep(?SLEEP), %% so that they are connected - - exit(Server, killed), - get_close(Server, ?LINE), - process_flag(trap_exit, false), - ssl_test_lib:close(LastClient). - -%%-------------------------------------------------------------------- -tls_client_closes_socket() -> - [{doc,"Test what happens when client closes socket before handshake is compleated"}]. - -tls_client_closes_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), + Server ! listen, - Connect = fun() -> - {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname, Port, [binary]]), - %% Make sure that ssl_accept is called before - %% client process ends and closes socket. - ct:sleep(?SLEEP) - end, - - _Client = spawn_link(Connect), - - ssl_test_lib:check_result(Server, {error,closed}). - -%%-------------------------------------------------------------------- -tls_closed_in_active_once() -> - [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. - -tls_closed_in_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - Port = ssl_test_lib:inet_port(node()), - Server = fun() -> - {ok, Listen} = gen_tcp:listen(Port, TcpOpts), - {ok, TcpServerSocket} = gen_tcp:accept(Listen), - {ok, ServerSocket} = ssl:ssl_accept(TcpServerSocket, ServerOpts), - lists:foreach( - fun(_) -> - ssl:send(ServerSocket, "some random message\r\n") - end, lists:seq(1, 20)), - %% Close TCP instead of SSL socket to trigger the bug: - gen_tcp:close(TcpServerSocket), - gen_tcp:close(Listen) - end, - spawn_link(Server), - {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), - Result = tls_closed_in_active_once_loop(Socket), - ssl:close(Socket), - case Result of - ok -> ok; - _ -> ct:fail(Result) - end. - -tls_closed_in_active_once_loop(Socket) -> - case ssl:setopts(Socket, [{active, once}]) of - ok -> - receive - {ssl, Socket, _} -> - tls_closed_in_active_once_loop(Socket); - {ssl_closed, Socket} -> - ok - after 5000 -> - no_ssl_closed_received - end; - {error, closed} -> - ok - end. -%%-------------------------------------------------------------------- -connect_dist() -> - [{doc,"Test a simple connect as is used by distribution"}]. - -connect_dist(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), - ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], - ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), - ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], + {Client1, #sslsocket{}} = + ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connect_dist_s, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connect_dist_c, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:check_result(Server, ok, Client1, ok), ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -clear_pem_cache() -> - [{doc,"Test that internal reference tabel is cleaned properly even when " - " the PEM cache is cleared" }]. -clear_pem_cache(Config) when is_list(Config) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [_,{FilRefDb, _} |_] = element(6, State), - {Server, Client} = basic_verify_test_no_close(Config), - CountReferencedFiles = fun({_, -1}, Acc) -> - Acc; - ({_, N}, Acc) -> - N + Acc - end, - - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl:clear_pem_cache(), - _ = sys:get_status(whereis(ssl_manager)), - {Server1, Client1} = basic_verify_test_no_close(Config), - 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server), ssl_test_lib:close(Client), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + ssl_test_lib:close(Client1). +defaults(Config) when is_list(Config)-> + Versions = ssl:versions(), + true = lists:member(sslv3, proplists:get_value(available, Versions)), + false = lists:member(sslv3, proplists:get_value(supported, Versions)), + true = lists:member('tlsv1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), + false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), + true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), + false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), + false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), + true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), + false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). -%%-------------------------------------------------------------------- fallback() -> [{doc, "Test TLS_FALLBACK_SCSV downgrade prevention"}]. @@ -1263,7 +217,6 @@ fallback(Config) when is_list(Config) -> | ClientOpts]}]), ssl_test_lib:check_server_alert(Server, Client, inappropriate_fallback). - %%-------------------------------------------------------------------- cipher_format() -> [{doc, "Test that cipher conversion from maps | tuples | stings to binarys works"}]. @@ -1277,175 +230,6 @@ cipher_format(Config) when is_list(Config) -> ssl:close(Socket2). %%-------------------------------------------------------------------- -suite_to_str() -> - [{doc, "Test that the suite_to_str API works"}]. -suite_to_str(Config) when is_list(Config) -> - "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" = - ssl:suite_to_str(#{key_exchange => null, - cipher => null, - mac => null, - prf => null}), - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdhe_ecdsa, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}), - "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdh_rsa, - cipher => aes_128_cbc, - mac => sha256, - prf => sha256}). - -%%-------------------------------------------------------------------- - -peername() -> - [{doc,"Test API function peername/1"}]. - -peername(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peername_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peername_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), - ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), - ServerMsg = {ok, {ClientIp, ClientPort}}, - ClientMsg = {ok, {ServerIp, Port}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -peercert() -> - [{doc,"Test API function peercert/1"}]. -peercert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - CertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - ClientMsg = {ok, BinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -peercert_result(Socket) -> - ssl:peercert(Socket). -%%-------------------------------------------------------------------- - -peercert_with_client_cert() -> - [{doc,"Test API function peercert/1"}]. -peercert_with_client_cert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), - ClientCertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), - - ServerMsg = {ok, ClientBinCert}, - ClientMsg = {ok, ServerBinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -sockname() -> - [{doc,"Test API function sockname/1"}]. -sockname(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, sockname_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, sockname_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = - case proplists:get_value(protocol, Config) of - dtls -> - %% DTLS sockets are not connected on the server side, - %% so we can only get a ClientIP, ServerIP will always be 0.0.0.0 - {0,0,0,0}; - _ -> - ssl_test_lib:node_to_hostip(ServerNode, server) - end, - - ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), - ServerMsg = {ok, {ServerIp, Port}}, - ClientMsg = {ok, {ClientIp, ClientPort}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -sockname_result(S) -> - ssl:sockname(S). - -%%-------------------------------------------------------------------- cipher_suites() -> [{doc,"Test API function cipher_suites/2, filter_cipher_suites/2" @@ -1542,2750 +326,95 @@ cipher_suites_mix(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -tls_socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. +unordered_protocol_versions_server() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -tls_socket_options(Config) when is_list(Config) -> +unordered_protocol_versions_server(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {packet, 0}, {header, 0}, - {active, true}], - %% Shall be the reverse order of Values! - Options = [active, header, packet, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - -tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), - ssl:setopts(Socket, [{nodelay, true}]), - {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -socket_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {active, true}], - %% Shall be the reverse order of Values! - Options = [active, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - - -socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -invalid_inet_get_option() -> - [{doc,"Test handling of invalid inet options in getopts"}]. - -invalid_inet_get_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -invalid_inet_get_option_not_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_not_list(Socket) -> - {error, {options, {socket_options, some_invalid_atom_here}}} - = ssl:getopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_get_option_improper_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_improper_list(Socket) -> - {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option() -> - [{doc,"Test handling of invalid inet options in setopts"}]. - -invalid_inet_set_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option(Socket) -> - {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), - {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), - {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), - {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), - ok. -%%-------------------------------------------------------------------- -invalid_inet_set_option_not_list() -> - [{doc,"Test handling of invalid type in setopts"}]. - -invalid_inet_set_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -set_invalid_inet_option_not_list(Socket) -> - {error, {options, {not_a_proplist, some_invalid_atom_here}}} - = ssl:setopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option_improper_list() -> - [{doc,"Test handling of invalid tye in setopts"}]. - -invalid_inet_set_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option_improper_list(Socket) -> - {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = - ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), - ok. - -%%-------------------------------------------------------------------- -tls_misc_ssl_options() -> - [{doc,"Test what happens when we give valid options"}]. - -tls_misc_ssl_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Check that ssl options not tested elsewhere are filtered away e.i. not passed to inet. - TestOpts = [{depth, 1}, - {key, undefined}, - {password, []}, - {reuse_session, fun(_,_,_,_) -> true end}, - {cb_info, {gen_tcp, tcp, tcp_closed, tcp_error}}], - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -ssl_options_not_proplist() -> - [{doc,"Test what happens if an option is not a key value tuple"}]. - -ssl_options_not_proplist(Config) when is_list(Config) -> - BadOption = {client_preferred_next_protocols, - client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, - {option_not_a_key_value_tuple, BadOption} = - ssl:connect("twitter.com", 443, [binary, {active, false}, - BadOption]). - -%%-------------------------------------------------------------------- -raw_ssl_option() -> - [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. - -raw_ssl_option(Config) when is_list(Config) -> - % 'raw' option values are platform-specific; these are the Linux values: - IpProtoTcp = 6, - % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. - TcpKeepIdle = 4, - KeepAliveTimeSecs = 55, - LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], - {ok, LSocket} = ssl:listen(0, LOptions), - % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify - % exactly which raw option we want, and the size of the buffer. - {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). - - -%%-------------------------------------------------------------------- -versions() -> - [{doc,"Test API function versions/0"}]. - -versions(Config) when is_list(Config) -> - [_|_] = Versions = ssl:versions(), - ct:log("~p~n", [Versions]). - - -%%-------------------------------------------------------------------- -eccs() -> - [{doc, "Test API functions eccs/0 and eccs/1"}]. - -eccs(Config) when is_list(Config) -> - [_|_] = All = ssl:eccs(), - [] = SSL3 = ssl:eccs(sslv3), - [_|_] = Tls = ssl:eccs(tlsv1), - [_|_] = Tls1 = ssl:eccs('tlsv1.1'), - [_|_] = Tls2 = ssl:eccs('tlsv1.2'), - [_|_] = Tls1 = ssl:eccs('dtlsv1'), - [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), - %% ordering is currently unverified by the test - true = lists:sort(All) =:= lists:usort(SSL3 ++ Tls ++ Tls1 ++ Tls2), - ok. - -%%-------------------------------------------------------------------- -send_recv() -> - [{doc,""}]. -send_recv(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_send_close() -> - [{doc,""}]. -tls_send_close(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname,Port,[binary, {active, false}]]), - {ok, SslS} = rpc:call(ClientNode, ssl, connect, - [TcpS,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), self(), Server]), - ok = ssl:send(SslS, "Hello world"), - {ok,<<"Hello world">>} = ssl:recv(SslS, 11), - gen_tcp:close(TcpS), - {error, _} = ssl:send(SslS, "Hello world"). - -%%-------------------------------------------------------------------- -version_option() -> - [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. -version_option(Config) when is_list(Config) -> - Versions = proplists:get_value(supported, ssl:versions()), - [version_option_test(Config, Version) || Version <- Versions]. - -%%-------------------------------------------------------------------- -close_transport_accept() -> - [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. - -close_transport_accept(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Port = 0, - Opts = [{active, false} | ServerOpts], - {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), - spawn_link(fun() -> - ct:sleep(?SLEEP), - rpc:call(ServerNode, ssl, close, [ListenSocket]) - end), - case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of - {error, closed} -> - ok; - Other -> - exit({?LINE, Other}) - end. -%%-------------------------------------------------------------------- -recv_active() -> - [{doc,"Test recv on active socket"}]. - -recv_active(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -recv_active_once() -> - [{doc,"Test recv on active (once) socket"}]. - -recv_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ServerOpts]}]), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - - - - -%%-------------------------------------------------------------------- -recv_active_n() -> - [{doc,"Test recv on active (n) socket"}]. - -recv_active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, 1} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, 1} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -%% Test case adapted from gen_tcp_misc_SUITE. -active_n() -> - [{doc,"Test {active,N} option"}]. - -active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - N = 3, - LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), - [{active,N}] = ok(ssl:getopts(LS, [active])), - active_n_common(LS, N), - Self = self(), - spawn_link(fun() -> - S0 = ok(ssl:transport_accept(LS)), - {ok, S} = ssl:handshake(S0), - ok = ssl:setopts(S, [{active,N}]), - [{active,N}] = ok(ssl:getopts(S, [active])), - ssl:controlling_process(S, Self), - Self ! {server, S} - end), - C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), - [{active,N}] = ok(ssl:getopts(C, [active])), - S = receive - {server, S0} -> S0 - after - 1000 -> - exit({error, connect}) - end, - active_n_common(C, N), - active_n_common(S, N), - ok = ssl:setopts(C, [{active,N}]), - ok = ssl:setopts(S, [{active,N}]), - ReceiveMsg = fun(Socket, Msg) -> - receive - {ssl,Socket,Msg} -> - ok; - {ssl,Socket,Begin} -> - receive - {ssl,Socket,End} -> - Msg = Begin ++ End, - ok - after 1000 -> - exit(timeout) - end - after 1000 -> - exit(timeout) - end - end, - repeat(3, fun(I) -> - Msg = "message "++integer_to_list(I), - ok = ssl:send(C, Msg), - ReceiveMsg(S, Msg), - ok = ssl:send(S, Msg), - ReceiveMsg(C, Msg) - end), - receive - {ssl_passive,S} -> - [{active,false}] = ok(ssl:getopts(S, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - receive - {ssl_passive,C} -> - [{active,false}] = ok(ssl:getopts(C, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - LS2 = ok(ssl:listen(0, [{active,0}])), - receive - {ssl_passive,LS2} -> - [{active,false}] = ok(ssl:getopts(LS2, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - ok = ssl:close(LS2), - ok = ssl:close(C), - ok = ssl:close(S), - ok = ssl:close(LS), - ok. - -active_n_common(S, N) -> - ok = ssl:setopts(S, [{active,-N}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,0}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - ok = ssl:setopts(S, [{active,32767}]), - {error,{options,_}} = ssl:setopts(S, [{active,1}]), - {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), - ok = ssl:setopts(S, [{active,-32768}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,true}]), - [{active,true}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,once}]), - [{active,once}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - {error,{options,_}} = ssl:setopts(S, [{active,32768}]), - ok = ssl:setopts(S, [{active,false}]), - [{active,false}] = ok(ssl:getopts(S, [active])), - ok. - -ok({ok,V}) -> V. - -repeat(N, Fun) -> - repeat(N, N, Fun). - -repeat(N, T, Fun) when is_integer(N), N > 0 -> - Fun(T-N), - repeat(N-1, T, Fun); -repeat(_, _, _) -> - ok. - -%%-------------------------------------------------------------------- -dh_params() -> - [{doc,"Test to specify DH-params file in server."}]. - -dh_params(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{dhfile, DHParamFile} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | - ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_upgrade() -> - [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. - -tls_upgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, [binary]}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -upgrade_result(Socket) -> - ssl:setopts(Socket, [{active, true}]), - ok = ssl:send(Socket, "Hello world"), - %% Make sure binary is inherited from tcp socket and that we do - %% not get the list default! - receive - {ssl, _, <<"H">>} -> - receive - {ssl, _, <<"ello world">>} -> - ok - end; - {ssl, _, <<"Hello world">>} -> - ok - end. - - -%%-------------------------------------------------------------------- -internal_active_1() -> - [{doc,"Test internal active 1 (behave as internal active once)"}]. - -internal_active_1(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{active, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{active, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_upgrade_with_timeout() -> - [{doc,"Test ssl_accept/3"}]. - -tls_upgrade_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_downgrade() -> - [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. -tls_downgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} |ClientOpts]}]), - - - ssl_test_lib:check_result(Server, ready, Client, ready), - - Server ! go, - Client ! go, - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -close_with_timeout() -> - [{doc,"Test normal (not downgrade) ssl:close/2"}]. -close_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options,[{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options, [{active, false} |ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - - -%%-------------------------------------------------------------------- -tls_tcp_connect() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. - -tls_tcp_connect(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}, {active, false}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]) - end - end. -%%-------------------------------------------------------------------- -tls_tcp_connect_big() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. - -tls_tcp_connect_big(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - - gen_tcp:send(Socket, <<?BYTE(0), - ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, timeout}} -> - ct:fail("hangs"); - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]); - {'EXIT', Server, _} -> - ok - end - end. - -%%-------------------------------------------------------------------- -ipv6() -> - [{require, ipv6_hosts}, - {doc,"Test ipv6."}]. -ipv6(Config) when is_list(Config) -> - {ok, Hostname0} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = - ssl_test_lib:run_where(Config, ipv6), - Server = ssl_test_lib:start_server([{node, ServerNode}, - {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client); - false -> - {skip, "Host does not support IPv6"} - end. - -%%-------------------------------------------------------------------- - -invalid_keyfile() -> - [{doc,"Test what happens with an invalid key file"}]. -invalid_keyfile(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - BadOpts = ssl_test_lib:ssl_options(server_bad_key, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, BadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, ClientOpts}]), - File = proplists:get_value(keyfile,BadOpts), - ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, - {error, closed}). + ServerMsg = ClientMsg = {ok,'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). %%-------------------------------------------------------------------- +unordered_protocol_versions_client() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -invalid_certfile() -> - [{doc,"Test what happens with an invalid cert file"}]. - -invalid_certfile(Config) when is_list(Config) -> +unordered_protocol_versions_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerBadOpts = ssl_test_lib:ssl_options(server_bad_cert, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerBadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - File = proplists:get_value(certfile, ServerBadOpts), - ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, - Client, {error, closed}). - - -%%-------------------------------------------------------------------- -invalid_cacertfile() -> - [{doc,"Test what happens with an invalid cacert file"}]. - -invalid_cacertfile(Config) when is_list(Config) -> - ClientOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(client_opts, Config)], - ServerBadOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(server_bad_ca, Config)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts}]), - - Port0 = ssl_test_lib:inet_port(Server0), - - - Client0 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - File0 = proplists:get_value(cacertfile, ServerBadOpts), - - ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, - Client0, {error, closed}), - - File = File0 ++ "do_not_exit.pem", - ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], - - Server1 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts1}]), - - Port1 = ssl_test_lib:inet_port(Server1), - - Client1 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - - ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, - Client1, {error, closed}), - ok. - - - -%%-------------------------------------------------------------------- -invalid_options() -> - [{doc,"Test what happens when we give invalid options"}]. - -invalid_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> - ssl_test_lib:check_result(Server, - {error, {options, {sslv2, Option}}}, - Client, - {error, {options, {sslv2, Option}}}); - (Client, Server, Option) -> - ssl_test_lib:check_result(Server, - {error, {options, Option}}, - Client, - {error, {options, Option}}) - end, - - TestOpts = - [{versions, [sslv2, sslv3]}, - {verify, 4}, - {verify_fun, function}, - {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, - {depth, four}, - {certfile, 'cert.pem'}, - {keyfile,'key.pem' }, - {password, foo}, - {cacertfile, ""}, - {dhfile,'dh.pem' }, - {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, - {renegotiate_at, "10"}, - {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, - {active, trice}, - {key, 'key.pem' }], - - [begin - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [TestOpt | ServerOpts]}]), - %% Will never reach a point where port is used. - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, - {host, Hostname}, {from, self()}, - {options, [TestOpt | ClientOpts]}]), - Check(Client, Server, TestOpt), - ok - end || TestOpt <- TestOpts], - ok. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -%%-------------------------------------------------------------------- -tls_shutdown() -> - [{doc,"Test API function ssl:shutdown/2"}]. -tls_shutdown(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_result, [server]}}, - {options, [{exit_on_close, false}, - {active, false} | ServerOpts]}]), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, ServerOpts }]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, tls_shutdown_result, [client]}}, - {options, - [{exit_on_close, false}, - {active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_shutdown_write() -> - [{doc,"Test API function ssl:shutdown/2 with option write."}]. -tls_shutdown_write(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_both() -> - [{doc,"Test API function ssl:shutdown/2 with option both."}]. -tls_shutdown_both(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_error() -> - [{doc,"Test ssl:shutdown/2 error handling"}]. -tls_shutdown_error(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - {ok, Listen} = ssl:listen(Port, ServerOpts), - {error, enotconn} = ssl:shutdown(Listen, read_write), - ok = ssl:close(Listen), - {error, closed} = ssl:shutdown(Listen, read_write). - -%%-------------------------------------------------------------------- -default_reject_anonymous()-> - [{doc,"Test that by default anonymous cipher suites are rejected "}]. -default_reject_anonymous(Config) when is_list(Config) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - TLSVersion = ssl_test_lib:tls_version(Version), - - [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- -reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). -%%-------------------------------------------------------------------- -reuse_session_expired() -> - [{doc,"Test sessions is not reused when it has expired"}]. -reuse_session_expired(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, 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, - - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE*2)), - - make_sure_expired(Hostname, Port0, SID), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client2, SID} -> - ct:fail(session_reused_when_session_expired); - {Client2, _} -> - ok - end, - process_flag(trap_exit, false), - ssl_test_lib:close(Server0), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - -make_sure_expired(Host, Port, Id) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - ClientCache = element(2, State), - - case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of - undefined -> - ok; - #session{is_resumable = false} -> - ok; - _ -> - ct:sleep(?SLEEP), - make_sure_expired(Host, Port, Id) - end. - -%%-------------------------------------------------------------------- -server_does_not_want_to_reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -server_does_not_want_to_reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuse_session, fun(_,_,_,_) -> - false - end} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - ssl_test_lib:close(Client0), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_does_not_want_to); - {Client1, _Other} -> - ok - end, - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -client_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_secure_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, true}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate_fallback() -> - [{doc,"Test that we can set secure_renegotiate to false that is " - "fallback option, we however do not have a insecure server to test against!"}]. -client_secure_renegotiate_fallback(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, false}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +connect_dist() -> + [{doc,"Test a simple connect as is used by distribution"}]. -%%-------------------------------------------------------------------- -server_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on server."}]. -server_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), +connect_dist(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), + ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], + ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), + ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Data = "From erlang to erlang", - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, + {mfa, {?MODULE, connect_dist_s, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. -client_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_reuse_session, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -server_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. -server_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_reuse_session, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -client_no_wrap_sequence_number() -> - [{doc,"Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -client_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to erlang", - N = 12, - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Version = ssl_test_lib:protocol_version(Config, tuple), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -server_no_wrap_sequence_number() -> - [{doc, "Test that erlang server will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -server_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - N = 12, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -der_input() -> - [{doc,"Test to input certs and key as der"}]. - -der_input(Config) when is_list(Config) -> - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [CADb | _] = element(6, State), - - Size = ets:info(CADb, size), - - SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - SeverVerifyOpts]), - ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - ClientVerifyOpts]), - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], - ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - Size = ets:info(CADb, size). - -%%-------------------------------------------------------------------- -der_input_opts(Opts) -> - Certfile = proplists:get_value(certfile, Opts), - CaCertsfile = proplists:get_value(cacertfile, Opts), - Keyfile = proplists:get_value(keyfile, Opts), - Dhfile = proplists:get_value(dhfile, Opts), - [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), - [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), - [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), - CaCerts = - lists:map(fun(Entry) -> - {_, CaCert, _} = Entry, - CaCert - end, ssl_test_lib:pem_to_der(CaCertsfile)), - {Cert, {Asn1Type, Key}, CaCerts, DHParams}. - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert() -> - [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. -no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered - ct:sleep(?SLEEP), - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | DsaServerOpts]}]), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert_file() -> - [{doc,"Check that a session is not reused if a server is restarted with a new " - "cert contained in a file with the same name as the old cert."}]. - -no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - NewServerOpts0 = new_config(PrivDir, ServerOpts), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, NewServerOpts0}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered and we get - %% new file time stamp when calling new_config! - ct:sleep(?SLEEP* 2), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - - ssl:clear_pem_cache(), - - NewServerOpts1 = new_config(PrivDir, DsaServerOpts), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | NewServerOpts1]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -defaults(Config) when is_list(Config)-> - Versions = ssl:versions(), - true = lists:member(sslv3, proplists:get_value(available, Versions)), - false = lists:member(sslv3, proplists:get_value(supported, Versions)), - true = lists:member('tlsv1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), - false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), - true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), - false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), - false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), - true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), - false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). - -%%-------------------------------------------------------------------- -reuseaddr() -> - [{doc,"Test reuseaddr option"}]. - -reuseaddr(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ClientOpts]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -tls_tcp_reuseaddr() -> - [{doc, "Reference test case."}]. -tls_tcp_reuseaddr(Config) when is_list(Config) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {transport, gen_tcp}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- - -honor_server_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_server_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], - honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}). - -honor_client_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_client_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], -honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}). - -honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} - | ClientOpts]}]), - - Version = ssl_test_lib:protocol_version(Config), - - ServerMsg = ClientMsg = {ok, {Version, Expected}}, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_ciphersuite_vs_version() -> - [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. -tls_ciphersuite_vs_version(Config) when is_list(Config) -> - - {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - ok = gen_tcp:send(Socket, - <<22, 3,0, 49:16, % handshake, SSL 3.0, length - 1, 45:24, % client_hello, length - 3,0, % SSL 3.0 - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, % no session ID - %% three cipher suites -- null, one with sha256 hash and one with sha hash - 6:16, 0,255, 0,61, 0,57, - 1, 0 % no compression - >>), - {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), - {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), - ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), - case ServerHello of - #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> - ok; - _ -> - ct:fail({unexpected_server_hello, ServerHello}) - end. - -%%-------------------------------------------------------------------- -conf_signature_algs() -> - [{doc,"Test to set the signature_algs option on both client and server"}]. -conf_signature_algs(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha, rsa}]} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha, rsa}]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -no_common_signature_algs() -> - [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. -no_common_signature_algs(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{signature_algs, [{sha256, rsa}]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{signature_algs, [{sha384, rsa}]} - | ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- - -tls_dont_crash_on_handshake_garbage() -> - [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " - "instead of crashing and exposing state to user code"}]. - -tls_dont_crash_on_handshake_garbage(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - unlink(Server), monitor(process, Server), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - - % Send hello and garbage record - ok = gen_tcp:send(Socket, - [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values - - <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage - ]), - % Send unexpected change_cipher_spec - ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), - - % Ensure we receive an alert, not sudden disconnect - {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). - -drop_handshakes(Socket, Timeout) -> - {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), - {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), - case RecType of - 22 -> drop_handshakes(Socket, Timeout); - _ -> {ok, <<Header/binary, Frag/binary>>} - end. - - -%%-------------------------------------------------------------------- - -hibernate() -> - [{doc,"Check that an SSL connection that is started with option " - "{hibernate_after, 1000} indeed hibernates after 1000ms of " - "inactivity"}]. - -hibernate(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{hibernate_after, 1000}|ClientOpts]}]), - {current_function, _} = - process_info(Pid, current_function), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ct:sleep(1500), - {current_function, {erlang, hibernate, 3}} = - process_info(Pid, current_function), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -hibernate_right_away() -> - [{doc,"Check that an SSL connection that is configured to hibernate " - "after 0 or 1 milliseconds hibernates as soon as possible and not " - "crashes"}]. - -hibernate_right_away(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - StartServerOpts = [{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}], - StartClientOpts = [return_socket, - {node, ClientNode}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}], - - Server1 = ssl_test_lib:start_server(StartServerOpts), - Port1 = ssl_test_lib:inet_port(Server1), - {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - - ct:sleep(1000), %% Schedule out - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid1, current_function), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - - Server2 = ssl_test_lib:start_server(StartServerOpts), - Port2 = ssl_test_lib:inet_port(Server2), - {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), - - ssl_test_lib:check_result(Server2, ok, Client2, ok), - - ct:sleep(1000), %% Schedule out - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid2, current_function), - - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client2). - -%%-------------------------------------------------------------------- -listen_socket() -> - [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. - -listen_socket(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ok, ListenSocket} = ssl:listen(0, ServerOpts), - - %% This can be a valid thing to do as - %% options are inherited by the accept socket - ok = ssl:controlling_process(ListenSocket, self()), - - {ok, _} = ssl:sockname(ListenSocket), - - {error, enotconn} = ssl:send(ListenSocket, <<"data">>), - {error, enotconn} = ssl:recv(ListenSocket, 0), - {error, enotconn} = ssl:connection_information(ListenSocket), - {error, enotconn} = ssl:peername(ListenSocket), - {error, enotconn} = ssl:peercert(ListenSocket), - {error, enotconn} = ssl:renegotiate(ListenSocket), - {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), - {error, enotconn} = ssl:shutdown(ListenSocket, read_write), - - ok = ssl:close(ListenSocket). -%%-------------------------------------------------------------------- -tls_ssl_accept_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -tls_ssl_accept_timeout(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {ssl_test_lib, - no_result_msg, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), - - receive - {tcp_closed, CSocket} -> - ssl_test_lib:check_result(Server, {error, timeout}), - receive - {'EXIT', Server, _} -> - %% Make sure supervisor had time to react on process exit - %% Could we come up with a better solution to this? - ct:sleep(500), - [] = supervisor:which_children(tls_connection_sup) - end - end. - -%%-------------------------------------------------------------------- -ssl_recv_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -ssl_recv_timeout(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, send_recv_result_timeout_server, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - send_recv_result_timeout_client, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -connect_twice() -> - [{doc,""}]. -connect_twice(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - Server ! listen, - - {Client1, #sslsocket{}} = - ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:check_result(Server, ok, Client1, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_active() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_active(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_passive() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_absolute() -> - [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. -renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{client_renegotiation, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_rejected, - []}}, + {mfa, {?MODULE, connect_dist_c, []}}, {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_tcp_error_propagation_in_active_mode() -> - [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. -tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, receive_msg, []}}, - {options, ClientOpts}]), - - {status, _, _, StatusInfo} = sys:get_status(Pid), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - StaticEnv = element(2, State), - Socket = element(11, StaticEnv), - %% Fake tcp error - Pid ! {tcp_error, Socket, etimedout}, - - ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). - -%%-------------------------------------------------------------------- -recv_error_handling() -> - [{doc,"Special case of call error handling"}]. -recv_error_handling(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, recv_close, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl:close(SslSocket), - ssl_test_lib:check_result(Server, ok). - - - -%%-------------------------------------------------------------------- -call_in_error_state() -> - [{doc,"Special case of call error handling"}]. -call_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), - receive - {Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - -run_client_error([Port, Opts]) -> - ssl:connect("localhost", Port, Opts). -run_error_server([ Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:controlling_process(Socket, self()). - -%%-------------------------------------------------------------------- - -close_in_error_state() -> - [{doc,"Special case of closing socket in error state"}]. -close_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), - receive - {_Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - ok -> - ok; - Other -> - ct:fail(Other) - end. -%%-------------------------------------------------------------------- -abuse_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -abuse_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:check_result(Server, ok, Client, ok), - Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -controlling_process_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -controlling_process_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server). - -%%-------------------------------------------------------------------- -run_error_server_close([Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:close(Socket). - -%%-------------------------------------------------------------------- - -rizzo() -> - [{doc, "Test that there is a 1/n-1-split for non RC4 in 'TLS < 1.1' as it is - vunrable to Rizzo/Dungon attack"}]. - -rizzo(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). -%%-------------------------------------------------------------------- -no_rizzo_rc4() -> - [{doc,"Test that there is no 1/n-1-split for RC4 as it is not vunrable to Rizzo/Dungon attack"}]. - -no_rizzo_rc4(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - %% Test uses RSA certs - Ciphers = ssl:filter_cipher_suites(ssl_test_lib:rc4_suites(NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_one_n_minus_one() -> - [{doc,"Test that the 1/n-1-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_one_n_minus_one(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - %% TODO: remove this clause when chacha is fixed! - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). - -rizzo_zero_n() -> - [{doc,"Test that the 0/n-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_zero_n(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_disabled() -> - [{doc,"Test that the mitigation of Rizzo/Dungon attack can be explicitly disabled"}]. - -rizzo_disabled(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -%%-------------------------------------------------------------------- -new_server_wants_peer_cert() -> - [{doc, "Test that server configured to do client certification does" - " not reuse session without a client certificate."}]. -new_server_wants_peer_cert(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - VServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, [ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, VServerOpts}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [ClientOpts]}]), - - CertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - Sever1Msg = {ok, BinCert}, - - ssl_test_lib:check_result(Server, ServerMsg, Server1, Sever1Msg), - - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -session_cache_process_list() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_list(Config) when is_list(Config) -> - session_cache_process(list,Config). -%%-------------------------------------------------------------------- -session_cache_process_mnesia() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_mnesia(Config) when is_list(Config) -> - session_cache_process(mnesia,Config). +eccs() -> + [{doc, "Test API functions eccs/0 and eccs/1"}]. -%%-------------------------------------------------------------------- +eccs(Config) when is_list(Config) -> + [_|_] = All = ssl:eccs(), + [] = ssl:eccs(sslv3), + [_|_] = Tls = ssl:eccs(tlsv1), + [_|_] = Tls1 = ssl:eccs('tlsv1.1'), + [_|_] = Tls2 = ssl:eccs('tlsv1.2'), + [_|_] = Tls1 = ssl:eccs('dtlsv1'), + [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), + %% ordering is currently not verified by the test + true = lists:sort(All) =:= lists:usort(Tls ++ Tls1 ++ Tls2), + ok. tls_versions_option() -> [{doc,"Test API versions option to connect/listen."}]. @@ -4323,2140 +452,6 @@ tls_versions_option(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -unordered_protocol_versions_server() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, ClientOpts}]), - - ServerMsg = ClientMsg = {ok,'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -unordered_protocol_versions_client() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, ServerOpts }]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -max_handshake_size() -> - [{doc,"Test that we can set max_handshake_size to max value."}]. - -max_handshake_size(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{max_handshake_size, 8388607} |ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{max_handshake_size, 8388607} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - -%%-------------------------------------------------------------------- - -server_name_indication_option() -> - [{doc,"Test API server_name_indication option to connect."}]. -server_name_indication_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, disable} | - ClientOpts]} - ]), - - ssl_test_lib:check_result(Server, ok, Client0, ok), - Server ! listen, - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, Hostname} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client1, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1). -%%-------------------------------------------------------------------- - -accept_pool() -> - [{doc,"Test having an accept pool."}]. -accept_pool(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {accepters, 3}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server0), - [Server1, Server2] = ssl_test_lib:accepters(2), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), - - ssl_test_lib:close(Server0), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - -%%-------------------------------------------------------------------- -%% TLS 1.3 -%%-------------------------------------------------------------------- - -tls13_enable_client_side() -> - [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. - -tls13_enable_client_side(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.1', 'tlsv1.2']} | ServerOpts] }]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.3']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -tls13_enable_server_side() -> - [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. - -tls13_enable_server_side(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.3']} | ServerOpts] }]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.1']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -tls_record_1_3_encode_decode() -> - [{doc,"Test TLS 1.3 record encode/decode functions"}]. - -tls_record_1_3_encode_decode(_Config) -> - ConnectionStates = - #{current_read => - #{beast_mitigation => one_n_minus_one, - cipher_state => - {cipher_state, - <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, - 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, - <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, - 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, - undefined,undefined,undefined,16}, - client_verify_data => undefined,compression_state => undefined, - mac_secret => undefined,secure_renegotiation => undefined, - security_parameters => - {security_parameters, - <<19,2>>, - 0,8,2,undefined,undefined,undefined,undefined,undefined, - sha384,undefined,undefined, - {handshake_secret, - <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, - 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, - 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, - 157>>}, - undefined, - <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, - 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, - undefined}, - sequence_number => 0,server_verify_data => undefined}, - current_write => - #{beast_mitigation => one_n_minus_one, - cipher_state => - {cipher_state, - <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, - 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, - <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, - 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, - undefined,undefined,undefined,16}, - client_verify_data => undefined,compression_state => undefined, - mac_secret => undefined,secure_renegotiation => undefined, - security_parameters => - {security_parameters, - <<19,2>>, - 0,8,2,undefined,undefined,undefined,undefined,undefined, - sha384,undefined,undefined, - {handshake_secret, - <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, - 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, - 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, - 157>>}, - undefined, - <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, - 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, - undefined}, - sequence_number => 0,server_verify_data => undefined}}, - - PlainText = [11, - <<0,2,175>>, - <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, - 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, - 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, - 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, - 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, - 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, - 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, - 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, - 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, - 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, - 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, - 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, - 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, - 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, - 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, - 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, - 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, - 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, - 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, - 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, - 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, - 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, - 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, - 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, - 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, - 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, - 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, - 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, - 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, - 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, - 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, - 146,152,146,151,107,126,216,210,9,93,0,0>>], - - {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), - CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, - - {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = - tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), - - DecodedText = iolist_to_binary(PlainText), - ct:log("Decoded: ~p ~n", [DecodedText]), - ok. - -tls13_1_RTT_handshake() -> - [{doc,"Test TLS 1.3 1-RTT Handshake"}]. - -tls13_1_RTT_handshake(_Config) -> - %% ConnectionStates with NULL cipher - ConnStatesNull = - #{current_write => - #{security_parameters => - #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, - sequence_number => 0 - } - }, - - %% {client} construct a ClientHello handshake message: - %% - %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 - %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 - %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b - %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 - %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 - %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 - %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a - %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 - %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 - %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - %% - %% {client} send handshake record: - %% - %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba - %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 - %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 - %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 - %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 - %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d - %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af - %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 - %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 - %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - %% - %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb - %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 - %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 - %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 - %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 - %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d - %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d - %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e - %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 - %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - ClientHello = - hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 - ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 - 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b - 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 - 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 - 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 - 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a - af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 - 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 - 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), - - ClientHelloRecord = - %% Current implementation always sets - %% legacy_record_version to Ox0303 - hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb - 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 - ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 - 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 - 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 - 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d - e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d - 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e - 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 - 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), - - {CHEncrypted, _} = - tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull), - ClientHelloRecord = iolist_to_binary(CHEncrypted), - - %% {server} extract secret "early": - %% - %% salt: 0 (all zero octets) - %% - %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% - %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c - %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a - HKDFAlgo = sha256, - Salt = binary:copy(<<?BYTE(0)>>, 32), - IKM = binary:copy(<<?BYTE(0)>>, 32), - EarlySecret = - hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c - e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"), - - {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}), - - %% {client} create an ephemeral x25519 key pair: - %% - %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 - %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 - %% - %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d - %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c - CPublicKey = - hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d - ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"), - - %% {server} create an ephemeral x25519 key pair: - %% - %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 - %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e - %% - %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 - %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f - SPrivateKey = - hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 - 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"), - - SPublicKey = - hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 - 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"), - - %% {server} construct a ServerHello handshake message: - %% - %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 - %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e - %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 - %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 - %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - ServerHello = - hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60 - dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e - d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 - 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 - dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), - - %% {server} derive secret for handshake "tls13 derived": - %% - %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 - %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a - %% - %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba - %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba - Hash = - hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), - - Hash = crypto:hash(HKDFAlgo, <<>>), - - Info = - hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), - - Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)), - - Expanded = - hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba - b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"), - - Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo), - - %% {server} extract secret "handshake": - %% - %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 - %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba - %% - %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d - %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d - %% - %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b - %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - - %% salt = Expanded - HandshakeIKM = - hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d - 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"), - - HandshakeSecret = - hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b - 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"), - - HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519), - - {handshake_secret, HandshakeSecret} = - tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM, - {early_secret, EarlySecret}), - - %% {server} derive secret "tls13 c hs traffic": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 - %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e - %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 - - %% PRK = HandshakeSecret - CHSTHash = - hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - CHSTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 - 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - CHSTrafficSecret = - hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e - 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"), - - CHSH = <<ClientHello/binary,ServerHello/binary>>, - CHSTHash = crypto:hash(HKDFAlgo, CHSH), - CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), - - CHSTrafficSecret = - tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), - - %% {server} derive secret "tls13 s hs traffic": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 - %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d - %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - - %% PRK = HandshakeSecret - %% hash = CHSTHash - SHSTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 - 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - SHSTrafficSecret = - hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d - 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"), - - SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), - - SHSTrafficSecret = - tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), - - - %% {server} derive secret for master "tls13 derived": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 - %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 - - %% PRK = HandshakeSecret - %% hash = Hash - %% info = Info - MasterDeriveSecret = - hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 - 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"), - - MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo), - - %% {server} extract secret "master": - %% - %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 - %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 - %% - %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% - %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a - %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - - %% salt = MasterDeriveSecret - %% IKM = IKM - MasterSecret = - hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a - 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"), - - {master_secret, MasterSecret} = - tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}), - - %% {server} send handshake record: - %% - %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e - %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 - %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 - %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 - %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - %% - %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6 - %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 - %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 - %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 - %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - - %% payload = ServerHello - ServerHelloRecord = - hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6 - af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 - 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 - 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 - cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), - - {SHEncrypted, _} = - tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull), - ServerHelloRecord = iolist_to_binary(SHEncrypted), - - %% {server} derive write traffic keys for handshake data: - %% - %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 - %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e - %% e4 03 bc - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30 - - %% PRK = SHSTrafficSecret - WriteKeyInfo = - hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"), - - WriteKey = - hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"), - - WriteIVInfo = - hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"), - - WriteIV = - hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"), - - Cipher = aes_128_gcm, %% TODO: get from ServerHello - - WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)), - %% TODO: remove hardcoded IV size - WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12), - - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret), - - %% {server} construct an EncryptedExtensions handshake message: - %% - %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00 - %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c - %% 00 02 40 01 00 00 00 00 - %% - %% {server} construct a Certificate handshake message: - %% - %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 - %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 - %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 - %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 - %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 - %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 - %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f - %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 - %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c - %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 - %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 - %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 - %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 - %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 - %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 - %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a - %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea - %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 - %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be - %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b - %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 - %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00 - %% - %% {server} construct a CertificateVerify handshake message: - %% - %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c - %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a - %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 - %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b - %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 - %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a - %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 - EncryptedExtensions = - hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00 - 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c - 00 02 40 01 00 00 00 00"), - - Certificate = - hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 - 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 - 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 - 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 - 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 - 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 - 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f - 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 - d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c - 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 - 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 - 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 - ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 - 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 - 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 - 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a - 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea - e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 - 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be - c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b - 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 - 96 12 29 ac 91 87 b4 2b 4d e1 00 00"), - - CertificateVerify = - hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c - 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a - b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 - 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b - be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 - 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a - 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"), - - %% {server} calculate finished "tls13 finished": - %% - %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 - %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - %% - %% hash (0 octets): (empty) - %% - %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 - %% 64 00 - %% - %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 - %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8 - %% - %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 - %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 - - %% PRK = SHSTrafficSecret - FInfo = - hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 - 64 00"), - - FExpanded = - hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 - c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"), - - FinishedVerifyData = - hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 - de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"), - - FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)), - - FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo), - - MessageHistory0 = [CertificateVerify, - Certificate, - EncryptedExtensions, - ServerHello, - ClientHello], - - FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0), - - %% {server} construct a Finished handshake message: - %% - %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb - %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 - %% 18 - FinishedHSBin = - hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb - dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 - 18"), - - FinishedHS = #finished{verify_data = FinishedVerifyData}, - - FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}), - FinishedHSBin = iolist_to_binary(FinishedIOList), - - %% {server} derive secret "tls13 c ap traffic": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 - %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce - %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 - - %% PRK = MasterSecret - CAPTHash = - hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - CAPTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 - 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - CAPTrafficSecret = - hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce - 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"), - - CHSF = <<ClientHello/binary, - ServerHello/binary, - EncryptedExtensions/binary, - Certificate/binary, - CertificateVerify/binary, - FinishedHSBin/binary>>, - - CAPTHash = crypto:hash(HKDFAlgo, CHSF), - - CAPTInfo = - tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - CAPTrafficSecret = - tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive secret "tls13 s ap traffic": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 - %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 - %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 - - %% PRK = MasterSecret - %% hash = CAPTHash - SAPTInfo = - hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 - 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - SAPTrafficSecret = - hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 - 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"), - - SAPTInfo = - tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - SAPTrafficSecret = - tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive secret "tls13 exp master": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 - %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 - %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 - %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 - - %% PRK = MasterSecret - %% hash = CAPTHash - ExporterInfo = - hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 - 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 - 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - ExporterMasterSecret = - hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 - 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"), - - ExporterInfo = - tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - ExporterMasterSecret = - tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive write traffic keys for application data: - %% - %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 - %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac - %% 92 e3 56 - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84 - - %% PRK = SAPTrafficsecret - %% key info = WriteKeyInfo - %% iv info = WrtieIVInfo - SWKey = - hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"), - - SWIV = - hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"), - - {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret), - - %% {server} derive read traffic keys for handshake data: - %% - %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f - %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 - %% 25 8d 01 - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f - - %% PRK = CHSTrafficsecret - %% key info = WriteKeyInfo - %% iv info = WrtieIVInfo - SRKey = - hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"), - - SRIV = - hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"), - - {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret). - - -tls13_finished_verify_data() -> - [{doc,"Test TLS 1.3 Finished message handling"}]. - -tls13_finished_verify_data(_Config) -> - ClientHello = - hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09 - 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 - 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 - e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 - f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01 - 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d - 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 - 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00 - 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06 - 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72 - d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed - 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00 - 02 01 01 00 2b 00 03 02 03 04"), - - ServerHello = - hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79 - 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 - 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 - e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 - f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24 - 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b - 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 - 28 80 b6 15 00 2b 00 02 03 04"), - - EncryptedExtensions = - hexstr2bin("08 00 00 02 00 00"), - - Certificate = - hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30 - 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 - 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 - 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 - 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 - 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 - 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 - 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 - 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 - 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e - 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d - 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 - 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 - b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 - 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d - bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d - f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 - f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b - 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 - f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 - ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 - ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 - 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb - 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 - 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 - d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad - 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 - 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 - ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 - 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 - 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 - 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 - 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 - cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 - 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 - 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b - fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 - 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd - 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 - f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 - 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 - 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 - 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 - ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e - 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 - c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 - cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 - 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f - 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 - 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 - 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0 - 00 00"), - - CertificateVerify = - hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d - 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75 - 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8 - 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13 - 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73 - df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b - 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2 - 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84 - fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c - 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5 - 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79 - ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a - aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7 - 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b - 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36 - c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe - c7 e4 a7 30 f1 36 9f 9e"), - - BaseKey = - hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4 - 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"), - - VerifyData = - hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea - c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"), - - Messages = [CertificateVerify, - Certificate, - EncryptedExtensions, - ServerHello, - ClientHello], - - FinishedKey = tls_v1:finished_key(BaseKey, sha256), - VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). - - -tls12_ssl_server_tls13_ssl_client() -> - [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client"}]. - -tls12_ssl_server_tls13_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2']}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {signature_algs_cert, [ecdsa_secp384r1_sha384, - rsa_pss_rsae_sha256, - rsa_pkcs1_sha256, - {sha256,rsa},{sha256,dsa}]}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_basic_ssl_server_openssl_client() -> - [{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client"}]. - -tls13_basic_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_basic_ssl_server_ssl_client() -> - [{doc,"Test TLS 1.3 basic connection between ssl server and ssl client"}]. - -tls13_basic_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_basic_openssl_server_ssl_client() -> - [{doc,"Test TLS 1.3 basic connection between openssl server and ssl client"}]. - -tls13_basic_openssl_server_ssl_client(Config) -> - 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 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-tls1_3", - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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). - - -tls13_custom_groups_ssl_server_openssl_client() -> - [{doc,"Test that ssl server can select a common group for key-exchange"}]. - -tls13_custom_groups_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - ClientOpts = [{groups,"P-384:P-256:X25519"}|ClientOpts0], - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_custom_groups_ssl_server_ssl_client() -> - [{doc,"Test that ssl server can select a common group for key-exchange"}]. - -tls13_custom_groups_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], - ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1], - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hello_retry_request_ssl_server_openssl_client() -> - [{doc,"Test that ssl server can request a new group when the client's first key share" - "is not supported"}]. - -tls13_hello_retry_request_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0], - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hello_retry_request_ssl_server_ssl_client() -> - [{doc,"Test that ssl server can request a new group when the client's first key share" - "is not supported"}]. - -tls13_hello_retry_request_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, x25519]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [secp256r1, x25519]}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_client_auth_empty_cert_alert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_empty_cert_alert_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_client_auth_empty_cert_alert_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts2], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_empty_cert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_client_auth_empty_cert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_empty_cert_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_client_auth_empty_cert_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts2], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication."}]. - -tls13_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication."}]. - -tls13_client_auth_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - %%Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [secp256r1, x25519]}|ClientOpts2], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_hrr_client_auth_empty_cert_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}, - {supported_groups, [x448, x25519]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [secp256r1, x25519]}|ClientOpts2], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}]. - -tls13_hrr_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_ssl_server_ssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}]. - -tls13_hrr_client_auth_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [secp256r1, x25519]}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. - -tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - %% Skip rsa_pkcs1_sha256! - {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_server_alert(Server, insufficient_security), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. - -tls13_unsupported_sign_algo_client_auth_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - %% Skip rsa_pkcs1_sha256! - {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, insufficient_security), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -%% Triggers a Server Alert as openssl s_client does not have a certificate with a -%% signature algorithm supported by the server (signature_algorithms_cert extension -%% of CertificateRequest does not contain the algorithm of the client certificate). -%% openssl s_client sends an empty certificate. -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. - -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {log_level, debug}, - {verify, verify_peer}, - {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, - %% Skip rsa_pkcs1_sha256! - {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -%% Triggers a Server Alert as ssl client does not have a certificate with a -%% signature algorithm supported by the server (signature_algorithms_cert extension -%% of CertificateRequest does not contain the algorithm of the client certificate). -%% ssl client sends an empty certificate. -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. - -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {log_level, debug}, - {verify, verify_peer}, - {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, - %% Skip rsa_pkcs1_sha256! - {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, certificate_required), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_connection_information() -> - [{doc,"Test the API function ssl:connection_information/1 in a TLS 1.3 connection"}]. - -tls13_connection_information(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_ssl_server_with_alpn_ssl_client() -> - [{doc,"Test TLS 1.3 between ssl server with ALPN configured and ssl client"}]. - -tls13_ssl_server_with_alpn_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_preferred_protocols, [<<5,6>>, <<1>>]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_ssl_server_with_alpn_ssl_client_empty_alpn() -> - [{doc,"Test TLS 1.3 between ssl server with ALPN configured and ssl client with empty ALPN"}]. - -tls13_ssl_server_with_alpn_ssl_client_empty_alpn(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_preferred_protocols, [<<5,6>>, <<1>>]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_advertised_protocols, []}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, no_application_protocol), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_ssl_server_with_alpn_ssl_client_bad_alpn() -> - [{doc,"Test TLS 1.3 between ssl server with ALPN configured and ssl client with bad ALPN"}]. - -tls13_ssl_server_with_alpn_ssl_client_bad_alpn(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_preferred_protocols, [<<5,6>>, <<1>>]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_advertised_protocols, [<<1,2,3,4>>]}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_server_alert(Server, no_application_protocol), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_ssl_server_with_alpn_ssl_client_alpn() -> - [{doc,"Test TLS 1.3 between ssl server with ALPN configured and ssl client with correct ALPN"}]. - -tls13_ssl_server_with_alpn_ssl_client_alpn(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_preferred_protocols, [<<5,6>>, <<1>>]}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {alpn_advertised_protocols, [<<1,2,3,4>>, <<5,6>>]}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_ecdsa_ssl_server_openssl_client() -> - [{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client using ECDSA certificates"}]. - -tls13_ecdsa_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_ecdsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_ecdsa_ssl_server_ssl_client() -> - [{doc,"Test TLS 1.3 basic connection between ssl server and ssl client using ECDSA certificates"}]. - -tls13_ecdsa_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_ecdsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_ecdsa_openssl_server_ssl_client() -> - [{doc,"Test TLS 1.3 basic connection between openssl server and ssl client using ECDSA certificates"}]. - -tls13_ecdsa_openssl_server_ssl_client(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_ecdsa_verify_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-tls1_3", - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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). - - -tls13_ecdsa_client_auth_ssl_server_ssl_client() -> - [{doc,"TLS 1.3: Test client authentication with ECDSA certificates."}]. - -tls13_ecdsa_client_auth_ssl_server_ssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_ecdsa_opts, Config), - - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - %%Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - - -%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- send_recv_result(Socket) -> @@ -6468,491 +463,9 @@ tcp_send_recv_result(Socket) -> {ok,"Hello world"} = gen_tcp:recv(Socket, 11), ok. -basic_verify_test_no_close(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - {Server, Client}. - -basic_test(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -prf_create_plan(TlsVersions, PRFs, Results) -> - lists:foldl(fun(Ver, Acc) -> - A = prf_ciphers_and_expected(Ver, PRFs, Results), - [A|Acc] - end, [], TlsVersions). -prf_ciphers_and_expected(TlsVer, PRFs, Results) -> - case TlsVer of - TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 - orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> - Ciphers = ssl:cipher_suites(), - {_, Expected} = lists:keyfind(md5sha, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; - TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> - lists:foldl( - fun(PRF, Acc) -> - Ciphers = prf_get_ciphers(TlsVer, PRF), - case Ciphers of - [] -> - ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), - Acc; - Ciphers -> - {_, Expected} = lists:keyfind(PRF, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, - {prf, PRF}] | Acc] - end - end, [], PRFs) - end. -prf_get_ciphers(_, PRF) -> - lists:filter( - fun(C) when tuple_size(C) == 4 andalso - element(4, C) == PRF -> - true; - (_) -> - false - end, - ssl:cipher_suites()). -prf_run_test(_, TlsVer, [], _, Prf) -> - ct:fail({error, cipher_list_empty, TlsVer, Prf}); -prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], - ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), - ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), - Server = ssl_test_lib:start_server( - [{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client( - [{node, ClientNode}, {port, Port}, - {host, Hostname}, {from, self()}, - {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -prf_verify_value(Socket, TlsVer, Expected, Algo) -> - Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), - case TlsVer of - sslv3 -> - case Ret of - {error, undefined} -> ok; - _ -> - {error, {expected, {error, undefined}, - got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} - end; - _ -> - case Ret of - {ok, Expected} -> ok; - {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, - prf_algorithm, Algo}} - end - end. - -send_recv_result_timeout_client(Socket) -> - {error, timeout} = ssl:recv(Socket, 11, 500), - {error, timeout} = ssl:recv(Socket, 11, 0), - ssl:send(Socket, "Hello world"), - receive - Msg -> - io:format("Msg ~p~n",[Msg]) - after 500 -> - ok - end, - {ok, "Hello world"} = ssl:recv(Socket, 11, 500), - ok. -send_recv_result_timeout_server(Socket) -> - ssl:send(Socket, "Hello"), - {ok, "Hello world"} = ssl:recv(Socket, 11), - ssl:send(Socket, " world"), - ok. - -recv_close(Socket) -> - {error, closed} = ssl:recv(Socket, 11), - receive - {_,{error,closed}} -> - error_extra_close_sent_to_user_process - after 500 -> - ok - end. - - -send_recv_result_active_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - -send_recv_result_active_no_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - - -ssl_active_recv(N) -> - ssl_active_recv(N, []). - -ssl_active_recv(0, Acc) -> - Acc; -ssl_active_recv(N, Acc) -> - receive - {ssl, _, Bytes} -> - ssl_active_recv(N-length(Bytes), Acc ++ Bytes) - end. - result_ok(_Socket) -> ok. -renegotiate(Socket, Data) -> - ct:log("Renegotiating ~n", []), - Result = ssl:renegotiate(Socket), - ct:log("Result ~p~n", [Result]), - ssl:send(Socket, Data), - case Result of - ok -> - ok; - Other -> - Other - end. - -renegotiate_reuse_session(Socket, Data) -> - %% Make sure session is registered - ct:sleep(?SLEEP), - renegotiate(Socket, Data). - -renegotiate_immediately(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - ok = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), - ok = ssl:renegotiate(Socket), - ct:log("Renegotiated again"), - ssl:send(Socket, "Hello world"), - ok. - -renegotiate_rejected(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:log("Failed to renegotiate again"), - ssl:send(Socket, "Hello world"), - ok. - -rizzo_add_mitigation_option(Value, Config) -> - lists:foldl(fun(Opt, Acc) -> - case proplists:get_value(Opt, Acc) of - undefined -> Acc; - C -> - N = lists:keystore(beast_mitigation, 1, C, - {beast_mitigation, Value}), - lists:keystore(Opt, 1, Acc, {Opt, N}) - end - end, Config, - [client_opts, client_dsa_opts, server_opts, server_dsa_opts, - server_ecdsa_opts, server_ecdh_rsa_opts]). - -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]. - -session_cache_process(_Type,Config) when is_list(Config) -> - reuse_session(Config). - -init([Type]) -> - ets:new(ssl_test, [named_table, public, set]), - ets:insert(ssl_test, {type, Type}), - case Type of - list -> - spawn(fun() -> session_loop([]) end); - mnesia -> - mnesia:start(), - {atomic,ok} = mnesia:create_table(sess_cache, []), - sess_cache - end. - -session_cb() -> - [{type, Type}] = ets:lookup(ssl_test, type), - Type. - -terminate(Cache) -> - case session_cb() of - list -> - Cache ! terminate; - mnesia -> - catch {atomic,ok} = - mnesia:delete_table(sess_cache) - end. - -lookup(Cache, Key) -> - case session_cb() of - list -> - Cache ! {self(), lookup, Key}, - receive {Cache, Res} -> Res end; - mnesia -> - case mnesia:transaction(fun() -> - mnesia:read(sess_cache, - Key, read) - end) of - {atomic, [{sess_cache, Key, Value}]} -> - Value; - _ -> - undefined - end - end. - -update(Cache, Key, Value) -> - case session_cb() of - list -> - Cache ! {update, Key, Value}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:write(sess_cache, - {sess_cache, Key, Value}, write) - end) - end. - -delete(Cache, Key) -> - case session_cb() of - list -> - Cache ! {delete, Key}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:delete(sess_cache, Key) - end) - end. - -foldl(Fun, Acc, Cache) -> - case session_cb() of - list -> - Cache ! {self(),foldl,Fun,Acc}, - receive {Cache, Res} -> Res end; - mnesia -> - Foldl = fun() -> - mnesia:foldl(Fun, Acc, sess_cache) - end, - {atomic, Res} = mnesia:transaction(Foldl), - Res - end. - -select_session(Cache, PartialKey) -> - case session_cb() of - list -> - Cache ! {self(),select_session, PartialKey}, - receive - {Cache, Res} -> - Res - end; - mnesia -> - Sel = fun() -> - mnesia:select(Cache, - [{{sess_cache,{PartialKey,'$1'}, '$2'}, - [],['$$']}]) - end, - {atomic, Res} = mnesia:transaction(Sel), - Res - end. - -session_loop(Sess) -> - receive - terminate -> - ok; - {Pid, lookup, Key} -> - case lists:keysearch(Key,1,Sess) of - {value, {Key,Value}} -> - Pid ! {self(), Value}; - _ -> - Pid ! {self(), undefined} - end, - session_loop(Sess); - {update, Key, Value} -> - NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)], - session_loop(NewSess); - {delete, Key} -> - session_loop(lists:keydelete(Key,1,Sess)); - {Pid,foldl,Fun,Acc} -> - Res = lists:foldl(Fun, Acc,Sess), - Pid ! {self(), Res}, - session_loop(Sess); - {Pid,select_session,PKey} -> - Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 -> - [[Id, Session]|Acc]; - (_,Acc) -> - Acc - end, - Sessions = lists:foldl(Sel, [], Sess), - Pid ! {self(), Sessions}, - session_loop(Sess) - end. - - -erlang_ssl_receive(Socket, Data) -> - case ssl_test_lib:active_recv(Socket, length(Data)) of - Data -> - ok; - Other -> - ct:fail({{expected, Data}, {got, Other}}) - end. - -receive_msg(_) -> - receive - Msg -> - Msg - end. - -controlling_process_result(Socket, Pid, Msg) -> - ok = ssl:controlling_process(Socket, Pid), - %% Make sure other side has evaluated controlling_process - %% before message is sent - ct:sleep(?SLEEP), - ssl:send(Socket, Msg), - no_result_msg. - - -controller_dies_result(_Socket, _Pid, _Msg) -> - receive Result -> Result end. - -get_close(Pid, Where) -> - receive - {'EXIT', Pid, _Reason} -> - receive - {_, {ssl_closed, Socket}} -> - ct:log("Socket closed ~p~n",[Socket]); - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end; - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end. - -run_send_recv_rizzo(Ciphers, Config, Version, Mfa) -> - Result = lists:map(fun(Cipher) -> - rizzo_test(Cipher, Config, Version, Mfa) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -rizzo_test(Cipher, Config, Version, Mfa) -> - {ClientOpts, ServerOpts} = client_server_opts(Cipher, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}, - {versions, [Version]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}| ClientOpts]}]), - - Result = ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - case Result of - ok -> - []; - Error -> - [{Cipher, Error}] - end. - -client_server_opts(#{key_exchange := KeyAlgo}, Config) - when KeyAlgo == rsa orelse - KeyAlgo == dhe_rsa orelse - KeyAlgo == ecdhe_rsa orelse - KeyAlgo == rsa_psk orelse - KeyAlgo == srp_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == dss orelse KeyAlgo == dhe_dss -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_ecdsa orelse KeyAlgo == ecdhe_ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}. - -connection_information_result(Socket) -> - {ok, Info = [_ | _]} = ssl:connection_information(Socket), - case length(Info) > 3 of - true -> - %% Atleast one ssl_option() is set - ct:log("Info ~p", [Info]), - ok; - false -> - ct:fail(no_ssl_options_returned) - end. - -connection_info_result(Socket) -> - {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), - {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. protocol_info_result(Socket) -> {ok, [{protocol, PVersion}]} = ssl:connection_information(Socket, [protocol]), @@ -6962,11 +475,7 @@ version_info_result(Socket) -> {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. -secret_connection_info_result(Socket) -> - {ok, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]} - = ssl:connection_information(Socket, [client_random, server_random, master_secret]), - is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret). - + connect_dist_s(S) -> Msg = term_to_binary({erlang,term}), ok = ssl:send(S, Msg). @@ -6976,88 +485,11 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. -tls_downgrade_result(Socket, Pid) -> - ok = ssl_test_lib:send_recv_result(Socket), - Pid ! {self(), ready}, - receive - go -> - ok - end, - case ssl:close(Socket, {self(), 10000}) of - {ok, TCPSocket} -> - inet:setopts(TCPSocket, [{active, true}]), - gen_tcp:send(TCPSocket, "Downgraded"), - receive - {tcp, TCPSocket, <<"Downgraded">>} -> - ok; - {tcp_closed, TCPSocket} -> - ct:fail("Peer timed out, downgrade aborted"), - ok; - Other -> - {error, Other} - end; - {error, timeout} -> - ct:fail("Timed out, downgrade aborted"), - ok; - Fail -> - {error, Fail} - end. - -tls_close(Socket) -> - ok = ssl_test_lib:send_recv_result(Socket), - case ssl:close(Socket, 5000) of - ok -> - ok; - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - - %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast -treashold(N, {3,0}) -> - (N div 2) + 1; -treashold(N, {3,1}) -> - (N div 2) + 1; -treashold(N, _) -> - N + 1. - -get_invalid_inet_option(Socket) -> - {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), - ok. - -tls_shutdown_result(Socket, server) -> - ssl:send(Socket, "Hej"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej hopp"} = ssl:recv(Socket, 8), - ok; - -tls_shutdown_result(Socket, client) -> - ssl:send(Socket, "Hej hopp"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej"} = ssl:recv(Socket, 3), - ok. - -tls_shutdown_write_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, write); -tls_shutdown_write_result(Socket, client) -> - ssl:recv(Socket, 0). - dummy(_Socket) -> %% Should not happen as the ssl connection will not be established %% due to fatal handshake failiure exit(kill). -tls_shutdown_both_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, read_write); -tls_shutdown_both_result(Socket, client) -> - ssl:recv(Socket, 0). - -peername_result(S) -> - ssl:peername(S). - version_option_test(Config, Version) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), @@ -7083,50 +515,3 @@ version_option_test(Config, Version) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -try_recv_active(Socket) -> - ssl:send(Socket, "Hello world"), - {error, einval} = ssl:recv(Socket, 11), - ok. -try_recv_active_once(Socket) -> - {error, einval} = ssl:recv(Socket, 11), - ok. - - -wait_for_send(Socket) -> - %% Make sure TLS process processed send message event - _ = ssl:connection_information(Socket). - -tls_or_dtls('dtlsv1') -> - dtls; -tls_or_dtls('dtlsv1.2') -> - dtls; -tls_or_dtls(_) -> - tls. - -hexstr2int(S) -> - B = hexstr2bin(S), - Bits = size(B) * 8, - <<Integer:Bits/integer>> = B, - Integer. - -hexstr2bin(S) when is_binary(S) -> - hexstr2bin(S, <<>>); -hexstr2bin(S) -> - hexstr2bin(list_to_binary(S), <<>>). -%% -hexstr2bin(<<>>, Acc) -> - Acc; -hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE - C =:= 10; %% LF - C =:= 13 -> %% CR - hexstr2bin(T, Acc); -hexstr2bin(<<X,Y,T/binary>>, Acc) -> - I = hex2int(X) * 16 + hex2int(Y), - hexstr2bin(T, <<Acc/binary,I>>). - -hex2int(C) when $0 =< C, C =< $9 -> - C - $0; -hex2int(C) when $A =< C, C =< $F -> - C - $A + 10; -hex2int(C) when $a =< C, C =< $f -> - C - $a + 10. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index 35efa2b8a3..a297539c36 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -174,7 +174,12 @@ do_test(Type, TC, Loop, ParallellConnections, Server) -> end, {TimeInMicro, _} = timer:tc(Run), TotalTests = ParallellConnections * Loop, - TestPerSecond = 1000000 * TotalTests div TimeInMicro, + TestPerSecond = case TimeInMicro of + 0 -> + undefined; + _ -> + 1000000 * TotalTests div TimeInMicro + end, io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]), unlink(SPid), SPid ! quit, diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl new file mode 100644 index 0000000000..fb1695f38a --- /dev/null +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -0,0 +1,563 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth, + missing_root_cert_auth, + missing_root_cert_auth_user_verify_fun_accept, + missing_root_cert_auth_user_verify_fun_reject, + verify_fun_always_run_client, + verify_fun_always_run_server, + incomplete_chain_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + false -> + {skip, "Missing EC crypto support"} + end; + +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang}, {version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth() -> + [{doc,"Must have ROOT certs to be able to verify verify peer"}]. +missing_root_cert_auth(Config) when is_list(Config) -> + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_cert_opts, Config)), + {ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ServerOpts]}]), + + ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}), + + ClientOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(client_cert_opts, Config)), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ClientOpts]}]), + + ssl_test_lib:check_result(Client, {error, {options, {cacertfile, ""}}}). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_accept() -> + [{doc, "Test that the client succeds if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that accepts the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> + {valid, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept() -> + [{doc, "Test old style verify fun"}]. + +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; + (Other, Acc) -> [Other | Acc] + end, + VerifyFun = + fun(ErrorList) -> + case lists:foldl(AcceptBadCa, [], ErrorList) of + [] -> true; + [_|_] -> false + end + end, + + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, VerifyFun}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_reject() -> + [{doc, "Test that the client fails if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that rejects the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_reject(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _UserState) -> + {fail, Reason}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, UserState}, _) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). +%%-------------------------------------------------------------------- +incomplete_chain_auth() -> + [{doc,"Test that we can verify an incompleat chain when we have the certs to rebuild it"}]. +incomplete_chain_auth(Config) when is_list(Config) -> + DefaultCertConf = ssl_test_lib:default_cert_chain_conf(), + #{client_config := ClientOpts0, + server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{server_chain, DefaultCertConf}, + {client_chain, DefaultCertConf}]), + [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerOpts0), + ClientCas = proplists:get_value(cacerts, ClientOpts0), + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, ServerCas ++ ClientCas} | + proplists:delete(cacerts, ClientOpts0)], Config), + ServerOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, [ServerRoot]} | + proplists:delete(cacerts, ServerOpts0)], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +verify_fun_always_run_client() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. + +verify_fun_always_run_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} + | ClientOpts]}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +verify_fun_always_run_server() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. +verify_fun_always_run_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test cases ----------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], + ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts1], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl new file mode 100644 index 0000000000..c88daa2185 --- /dev/null +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -0,0 +1,386 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_cert_tests). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + [{doc,"Test connection without authentication"}]. + +no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +auth() -> + [{doc,"Test connection with mutual authentication"}]. + +auth(Config) -> + ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to false."}]. + +client_auth_empty_cert_accepted(Config) -> + ClientOpts = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to true."}]. + +client_auth_empty_cert_rejected(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options([], Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts = proplists:delete(keyfile, ClientOpts1), + + Version = proplists:get_value(version,Config), + case Version of + 'tlsv1.3' -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required); + _ -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure) + end. +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + [{doc, "Client sends an incompleate chain, by default not acceptable."}]. + +client_auth_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts0)), + [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), + ClientOpts = [{cacerts, [RootCA]} | + proplists:delete(cacertfile, ClientOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. + +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), + + PartialChain = fun(CertChain) -> + case lists:member(IntermidiateCA, CertChain) of + true -> + {trusted_ca, IntermidiateCA}; + false -> + unknown_ca + end + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + + %%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " + "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. + +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + unknown_ca + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + + %%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. + +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + true = false %% crash on purpose + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + [{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}]. + +missing_root_cert_no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + [{doc,"Test server with invalid signature"}]. + +invalid_signature_client(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ClientOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ClientCertFile = proplists:get_value(certfile, ClientOpts0), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), + [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), + ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), + ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, + NewClientDerCert = public_key:pkix_sign(ClientOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]), + ClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{verify, verify_peer} | ServerOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +invalid_signature_server() -> + [{doc,"Test client with invalid signature"}]. + +invalid_signature_server(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ServerOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ServerCertFile = proplists:get_value(certfile, ServerOpts0), + NewServerCertFile = filename:join(PrivDir, "server_invalid_cert.pem"), + [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), + ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), + ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, + NewServerDerCert = public_key:pkix_sign(ServerOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), + ServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts0)], + ClientOpts = [{verify, verify_peer} | ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config_custom(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to false."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +group_config_custom(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:P-256:P-384"} | ServerOpts], + [{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts], + [{groups,"P-384:P-256:X25519"} | ClientOpts]} + end. + +group_config(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:X25519"} | ServerOpts], + [{supported_groups, [secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, x25519]} | ServerOpts], + [{groups,"P-256:X25519"} | ClientOpts]} + end. + +test_ciphers(_, 'tlsv1.3' = Version) -> + Ciphers = ssl:cipher_suites(default, Version), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers); +test_ciphers(Kex, Version) -> + Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version), + [{key_exchange, Kex}]), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers). + + + +openssl_ciphers() -> + Str = os:cmd("openssl ciphers"), + string:split(string:strip(Str, right, $\n), ":", all). diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 4de4a35e59..f38858e0bf 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -71,37 +71,20 @@ all_protocol_groups() -> {group, error_handling}]. tests() -> - [verify_peer, - verify_none, - server_require_peer_cert_ok, - server_require_peer_cert_fail, - server_require_peer_cert_empty_ok, - server_require_peer_cert_partial_chain, - server_require_peer_cert_allow_partial_chain, - server_require_peer_cert_do_not_allow_partial_chain, - server_require_peer_cert_partial_chain_fun_fail, - verify_fun_always_run_client, - verify_fun_always_run_server, - cert_expired, - invalid_signature_client, - invalid_signature_server, + [cert_expired, + %invalid_signature_client, + %%invalid_signature_server, extended_key_usage_verify_both, extended_key_usage_verify_server, critical_extension_verify_client, critical_extension_verify_server, critical_extension_verify_none, - customize_hostname_check, - incomplete_chain, long_chain ]. error_handling_tests()-> [client_with_cert_cipher_suites_handshake, - server_verify_no_cacerts, - unknown_server_ca_fail, - unknown_server_ca_accept_verify_none, - unknown_server_ca_accept_verify_peer, - unknown_server_ca_accept_backwardscompatibility, + %%unknown_server_ca_accept_backwardscompatibility, no_authority_key_identifier, no_authority_key_identifier_keyEncipherment]. @@ -160,61 +143,6 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- - -verify_peer() -> - [{doc,"Test option verify_peer"}]. -verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -verify_none() -> - [{doc,"Test option verify_none"}]. - -verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_none} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {verify, verify_none} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. @@ -253,309 +181,6 @@ server_verify_client_once(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_require_peer_cert_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_fail() -> - [{doc,"Test server option fail_if_no_peer_cert when peer doesn't send cert"}]. - -server_require_peer_cert_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - BadClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{active, Active} | ServerOpts]}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{active, Active} | BadClientOpts]}]), - - Version = proplists:get_value(version,Config), - case Version of - 'tlsv1.3' -> - ssl_test_lib:check_server_alert(Server, Client, certificate_required); - _ -> - ssl_test_lib:check_server_alert(Server, Client, handshake_failure) - end. - -%%-------------------------------------------------------------------- -server_require_peer_cert_empty_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_empty_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, false} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ClientOpts = proplists:delete(keyfile, proplists:delete(certfile, ClientOpts0)), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_partial_chain() -> - [{doc, "Client sends an incompleate chain, by default not acceptable."}]. - -server_require_peer_cert_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active}, - {cacerts, [RootCA]} | - proplists:delete(cacertfile, ClientOpts)]}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -server_require_peer_cert_allow_partial_chain() -> - [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. - -server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), - - PartialChain = fun(CertChain) -> - case lists:member(IntermidiateCA, CertChain) of - true -> - {trusted_ca, IntermidiateCA}; - false -> - unknown_ca - end - end, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - %%-------------------------------------------------------------------- -server_require_peer_cert_do_not_allow_partial_chain() -> - [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " - "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. - -server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - unknown_ca - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - %%-------------------------------------------------------------------- -server_require_peer_cert_partial_chain_fun_fail() -> - [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. - -server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - true = false %% crash on purpose - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -verify_fun_always_run_client() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. - -verify_fun_always_run_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we cannot tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). - -%%-------------------------------------------------------------------- -verify_fun_always_run_server() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. -verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we cannot tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). -%%-------------------------------------------------------------------- - cert_expired() -> [{doc,"Test server with expired certificate"}]. @@ -937,235 +562,6 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_verify_no_cacerts() -> - [{doc,"Test server must have cacerts if it wants to verify client"}]. -server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_rsa_opts, Config)), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{verify, verify_peer} - | ServerOpts]}]), - - ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}). - - -%%-------------------------------------------------------------------- -unknown_server_ca_fail() -> - [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. -unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, [test_to_update_user_state | UserState]}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - ssl_test_lib:check_client_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_none() -> - [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. -unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_none}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_peer() -> - [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" - " with a verify_fun that accepts the unknown ca error"}]. -unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> - {valid, UserState}; - (_,{bad_cert, _} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -unknown_server_ca_accept_backwardscompatibility() -> - [{doc,"Test that old style verify_funs will work"}]. -unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; - (Other, Acc) -> [Other | Acc] - end, - VerifyFun = - fun(ErrorList) -> - case lists:foldl(AcceptBadCa, [], ErrorList) of - [] -> true; - [_|_] -> false - end - end, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, VerifyFun}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -customize_hostname_check() -> - [{doc,"Test option customize_hostname_check."}]. -customize_hostname_check(Config) when is_list(Config) -> - Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = [{dNSName, "*.example.org"}], - critical = false} - ], - {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain, - [[], - [], - [{extensions, Ext}] - ]}], - Config, "https_hostname_convention"), - ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), - ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - CustomFun = public_key:pkix_verify_hostname_match_fun(https), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, "other.example.org"}, - {customize_hostname_check, - [{match_fun, CustomFun}]} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client, ok), - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts} - ]), - ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). - -incomplete_chain() -> - [{doc,"Test option verify_peer"}]. -incomplete_chain(Config) when is_list(Config) -> - DefConf = ssl_test_lib:default_cert_chain_conf(), - CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), - #{server_config := ServerConf, - client_config := ClientConf} = public_key:pkix_test_data(CertChainConf), - [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerConf), - ClientCas = proplists:get_value(cacerts, ClientConf), - - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer}, - {cacerts, [ServerRoot]} | - proplists:delete(cacerts, ServerConf)]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {verify, verify_peer}, - {cacerts, ServerCas ++ ClientCas} | - proplists:delete(cacerts, ClientConf)]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). long_chain() -> [{doc,"Test option verify_peer"}]. diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl index 51788c29e7..e598d662e9 100644 --- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl @@ -45,7 +45,7 @@ groups() -> {'tlsv1.2', [], kex()}, {'tlsv1.1', [], kex()}, {'tlsv1', [], kex()}, - {'sslv3', [], kex()}, + {'sslv3', [], ssl3_kex()}, {'dtlsv1.2', [], kex()}, {'dtlsv1', [], kex()}, {dhe_rsa, [],[dhe_rsa_3des_ede_cbc, @@ -130,6 +130,11 @@ groups() -> kex() -> rsa() ++ ecdsa() ++ dss() ++ anonymous(). + +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + + rsa() -> [{group, dhe_rsa}, {group, ecdhe_rsa}, @@ -138,6 +143,11 @@ rsa() -> {group, rsa_psk} ]. +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + ecdsa() -> [{group, ecdhe_ecdsa}]. @@ -145,6 +155,10 @@ dss() -> [{group, dhe_dss}, {group, srp_dss}]. +ssl3_dss() -> + [{group, dhe_dss} + ]. + anonymous() -> [{group, dh_anon}, {group, ecdh_anon}, @@ -154,6 +168,10 @@ anonymous() -> {group, srp_anon} ]. +ssl3_anonymous() -> + [{group, dh_anon}]. + + init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 003e1fc448..7cfb2ac0c5 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. 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. @@ -311,9 +311,11 @@ listen_port_options(Config) when is_list(Config) -> catch _:Reason -> stop_ssl_node(NH2), + stop_ssl_node(NH1), ct:fail(Reason) end, stop_ssl_node(NH2), + stop_ssl_node(NH1), success(Config). %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 1b432970b6..2750a4a9dc 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -214,7 +214,7 @@ encode_decode_srp(_Config) -> 0,3, % HostNameLength 98,97,114>>, % hostname = "bar" EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> = - ssl_handshake:encode_hello_extensions(Exts), + ssl_handshake:encode_hello_extensions(Exts, {3,3}), Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client). signature_algorithms(Config) -> diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl index 878e983bb9..8a76a3a82b 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_npn_handshake_SUITE). +-module(ssl_npn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 46734ba180..7dbc0c5134 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -24,10 +24,11 @@ %% Note: This directive should only be used in test suites. -compile(export_all). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("tls_handshake.hrl"). --include("tls_record.hrl"). + +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). -include_lib("common_test/include/ct.hrl"). %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 6f11e2bbe8..3c7f6ab20f 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -34,7 +34,10 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [pem_cleanup, invalid_insert]. + [ + pem_cleanup, + clear_pem_cache, + invalid_insert]. groups() -> []. @@ -110,6 +113,37 @@ pem_cleanup(Config)when is_list(Config) -> ssl_test_lib:close(Client), false = Size == Size1. +clear_pem_cache() -> + [{doc,"Test that internal reference tabel is cleaned properly even when " + " the PEM cache is cleared" }]. +clear_pem_cache(Config) when is_list(Config) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [_,{FilRefDb, _} |_] = element(6, State), + {Server, Client} = basic_verify_test_no_close(Config), + CountReferencedFiles = fun({_, -1}, Acc) -> + Acc; + ({_, N}, Acc) -> + N + Acc + end, + + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl:clear_pem_cache(), + _ = sys:get_status(whereis(ssl_manager)), + {Server1, Client1} = basic_verify_test_no_close(Config), + 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + invalid_insert() -> [{doc, "Test that insert of invalid pem does not cause empty cache entry"}]. invalid_insert(Config)when is_list(Config) -> @@ -163,3 +197,22 @@ later()-> Gregorian = calendar:datetime_to_gregorian_seconds(DateTime), calendar:gregorian_seconds_to_datetime(Gregorian + (2 * ?CLEANUP_INTERVAL)). +basic_verify_test_no_close(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + {Server, Client}. diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..ef3f9ebb52 --- /dev/null +++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl @@ -0,0 +1,499 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_renegotiate_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +-define(RENEGOTIATION_DISABLE_TIME, 12000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], renegotiate_tests()}, + {'dtlsv1', [], renegotiate_tests()}, + {'tlsv1.3', [], renegotiate_tests()}, + {'tlsv1.2', [], renegotiate_tests()}, + {'tlsv1.1', [], renegotiate_tests()}, + {'tlsv1', [], renegotiate_tests()}, + {'sslv3', [], ssl3_renegotiate_tests()} + ]. + +renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_secure_renegotiate, + client_secure_renegotiate_fallback, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +ssl3_renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +client_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_secure_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, true}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate_fallback() -> + [{doc,"Test that we can set secure_renegotiate to false that is " + "fallback option, we however do not have a insecure server to test against!"}]. +client_secure_renegotiate_fallback(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, false}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on server."}]. +server_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. +client_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_reuse_session, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +server_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. +server_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_reuse_session, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +client_no_wrap_sequence_number() -> + [{doc,"Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +client_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to erlang", + N = 12, + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Version = ssl_test_lib:protocol_version(Config, tuple), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_no_wrap_sequence_number() -> + [{doc, "Test that erlang server will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +server_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + N = 12, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +renegotiate_dos_mitigate_active() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_active(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_passive() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_absolute() -> + [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. +renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{client_renegotiation, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_rejected, + []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +renegotiate(Socket, Data) -> + ct:log("Renegotiating ~n", []), + Result = ssl:renegotiate(Socket), + ct:log("Result ~p~n", [Result]), + ssl:send(Socket, Data), + case Result of + ok -> + ok; + Other -> + Other + end. + +renegotiate_reuse_session(Socket, Data) -> + %% Make sure session is registered + ct:sleep(?SLEEP), + renegotiate(Socket, Data). + +renegotiate_immediately(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + ok = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), + ok = ssl:renegotiate(Socket), + ct:log("Renegotiated again"), + ssl:send(Socket, "Hello world"), + ok. + +renegotiate_rejected(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:log("Failed to renegotiate again"), + ssl:send(Socket, "Hello world"), + ok. + +%% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast +treashold(N, {3,0}) -> + (N div 2) + 1; +treashold(N, {3,1}) -> + (N div 2) + 1; +treashold(N, _) -> + N + 1. + +erlang_ssl_receive(Socket, Data) -> + case ssl_test_lib:active_recv(Socket, length(Data)) of + Data -> + ok; + Other -> + ct:fail({{expected, Data}, {got, Other}}) + end. diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl new file mode 100644 index 0000000000..aa79698a72 --- /dev/null +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -0,0 +1,377 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2019. 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_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("tls_handshake.hrl"). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], session_tests()}, + {'dtlsv1', [], session_tests()}, + {'tlsv1.3', [], session_tests()}, + {'tlsv1.2', [], session_tests()}, + {'tlsv1.1', [], session_tests()}, + {'tlsv1', [], session_tests()}, + {'sslv3', [], session_tests()} + ]. + +session_tests() -> + [reuse_session, + reuse_session_expired, + server_does_not_want_to_reuse_session, + no_reuses_session_server_restart_new_cert, + no_reuses_session_server_restart_new_cert_file]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + ssl_test_lib:make_dsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_expired, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, session_lifetime, ?EXPIRE), + application:set_env(ssl, session_delay_cleanup_time, 500), + ssl:start(), + ct:timetrap({seconds, 30}), + Config; +init_per_testcase(_, Config) -> + ct:timetrap({seconds, 15}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +reuse_session_expired() -> + [{doc,"Test sessions is not reused when it has expired"}]. +reuse_session_expired(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, 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, + + %% Make sure session is unregistered due to expiration + ct:sleep((?EXPIRE*2)), + + make_sure_expired(Hostname, Port0, SID), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, SID} -> + ct:fail(session_reused_when_session_expired); + {Client2, _} -> + ok + end, + process_flag(trap_exit, false), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +make_sure_expired(Host, Port, Id) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + + case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of + undefined -> + ok; + #session{is_resumable = false} -> + ok; + _ -> + ct:sleep(?SLEEP), + make_sure_expired(Host, Port, Id) + end. + +%%-------------------------------------------------------------------- +server_does_not_want_to_reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +server_does_not_want_to_reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, [{reuse_session, fun(_,_,_,_) -> + false + end} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(?SLEEP), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_does_not_want_to); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Client0), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + +no_reuses_session_server_restart_new_cert() -> + [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. +no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + DsaClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered + ct:sleep(?SLEEP), + Monitor = erlang:monitor(process, Server), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + receive + {'DOWN', Monitor, _, _, _} -> + ok + end, + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | DsaServerOpts]}]), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, DsaClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +no_reuses_session_server_restart_new_cert_file() -> + [{doc,"Check that a session is not reused if a server is restarted with a new " + "cert contained in a file with the same name as the old cert."}]. + +no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + NewServerOpts0 = ssl_test_lib:new_config(PrivDir, ServerOpts), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, NewServerOpts0}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered and we get + %% new file time stamp when calling new_config! + ct:sleep(?SLEEP* 2), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + + ssl:clear_pem_cache(), + + NewServerOpts1 = ssl_test_lib:new_config(PrivDir, DsaServerOpts), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | NewServerOpts1]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 7629d75100..e68ea2c99d 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -36,7 +36,6 @@ all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, - {group, 'sslv3'}, {group, 'dtlsv1.2'}, {group, 'dtlsv1'} ]. @@ -46,7 +45,6 @@ groups() -> {'tlsv1.2', [], sni_tests()}, {'tlsv1.1', [], sni_tests()}, {'tlsv1', [], sni_tests()}, - {'sslv3', [], sni_tests()}, {'dtlsv1.2', [], sni_tests()}, {'dtlsv1', [], sni_tests()} ]. @@ -61,7 +59,8 @@ sni_tests() -> dns_name, ip_fallback, no_ip_fallback, - dns_name_reuse]. + dns_name_reuse, + customize_hostname_check]. init_per_suite(Config0) -> catch crypto:stop(), @@ -88,12 +87,10 @@ end_per_suite(_) -> ssl:stop(), application:stop(crypto). -init_per_testcase(TestCase, Config) when TestCase == ip_fallback; - TestCase == no_ip_fallback; - TestCase == dns_name_reuse -> +init_per_testcase(customize_hostname_check, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), - ct:timetrap({seconds, 20}), + ssl_test_lib:clean_start(), + ct:timetrap({seconds, 5}), Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), @@ -236,7 +233,60 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), - ssl_test_lib:check_client_alert(Client1, handshake_failure). + ssl_test_lib:check_client_alert(Client1, handshake_failure), + ssl_test_lib:close(Client0). + + +customize_hostname_check() -> + [{doc,"Test option customize_hostname_check."}]. +customize_hostname_check(Config) when is_list(Config) -> + Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = [{dNSName, "*.example.org"}], + critical = false} + ], + #{server_config := ServerOpts0, + client_config := ClientOpts0} = ssl_test_lib:make_cert_chains_pem(rsa, [{server_chain, + [[], + [], + [{extensions, Ext}] + ]}], + Config, "https_hostname_convention"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + CustomFun = public_key:pkix_verify_hostname_match_fun(https), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{verify, verify_peer}, + {server_name_indication, "other.example.org"}, + {customize_hostname_check, + [{match_fun, CustomFun}]} | ClientOpts] + }]), + ssl_test_lib:check_result(Server, ok, Client, ok), + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{verify, verify_peer}, + {server_name_indication, "other.example.org"} | ClientOpts]} + ]), + ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl new file mode 100644 index 0000000000..d648f2f9e1 --- /dev/null +++ b/lib/ssl/test/ssl_socket_SUITE.erl @@ -0,0 +1,437 @@ +%% +%% Copyright Ericsson AB 2019-2019. 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_socket_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, tls}, + {group, dtls} + ]. + +groups() -> + [ + {tls,[], socket_tests() ++ raw_inet_opt()}, + {dtls,[], socket_tests()} + ]. + +socket_tests() -> + [ + getstat, + socket_options, + invalid_inet_get_option, + invalid_inet_get_option_not_list, + invalid_inet_get_option_improper_list, + invalid_inet_set_option, + invalid_inet_set_option_not_list, + invalid_inet_set_option_improper_list + ]. + +raw_inet_opt() -> + [ + raw_inet_option + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(dtls, Config) -> + [{protocol_opts, [{protocol, dtls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(tls, Config) -> + [{protocol_opts, [{protocol, tls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(_GroupName, Config) -> + [{client_type, erlang}, + {server_type, erlang} | Config]. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(raw_inet_option, Config) -> + ct:timetrap({seconds, 5}), + case os:type() of + {unix,linux} -> + Config; + _ -> + {skip, "Raw options are platform-specific"} + end; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +getstat() -> + [{doc,"Test API function getstat/2"}]. + +getstat(Config) when is_list(Config) -> + ClientOpts = ?config(client_rsa_opts, Config), + ServerOpts = ?config(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + Server2 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port2 = ssl_test_lib:inet_port(Server2), + {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port1,[{active, once}|ClientOpts]]), + {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port2,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", + [self(), self(), Server1, Server2]), + + %% We only check that the values are non-zero initially + %% (due to the handshake), and that sending more changes the values. + + %% Passive socket. + + {ok, InitialStats} = ssl:getstat(PassiveC), + ct:pal("InitialStats ~p~n", [InitialStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + ok = ssl:send(PassiveC, "Hello world"), + wait_for_send(PassiveC), + {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), + ct:pal("SStats ~p~n", [SStats]), + [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) + || Name <- [send_cnt, send_oct]]), + + %% Active socket. + + {ok, InitialAStats} = ssl:getstat(ActiveC), + ct:pal("InitialAStats ~p~n", [InitialAStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + _ = receive + {ssl, ActiveC, _} -> + ok + after + ?SLEEP -> + exit(timeout) + end, + + ok = ssl:send(ActiveC, "Hello world"), + wait_for_send(ActiveC), + {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), + ct:pal("ASStats ~p~n", [ASStats]), + [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) + || Name <- [send_cnt, send_oct]]), + + ok. +%%-------------------------------------------------------------------- +socket_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +socket_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {active, true}], + %% Shall be the reverse order of Values! + Options = [active, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). + +%%-------------------------------------------------------------------- +raw_inet_option() -> + [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. + +raw_inet_option(Config) when is_list(Config) -> + % 'raw' option values are platform-specific; these are the Linux values: + IpProtoTcp = 6, + % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. + TcpKeepIdle = 4, + KeepAliveTimeSecs = 55, + LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], + {ok, LSocket} = ssl:listen(0, LOptions), + % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify + % exactly which raw option we want, and the size of the buffer. + {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = + ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). + +%%-------------------------------------------------------------------- + +invalid_inet_get_option() -> + [{doc,"Test handling of invalid inet options in getopts"}]. + +invalid_inet_get_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_not_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_improper_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option() -> + [{doc,"Test handling of invalid inet options in setopts"}]. + +invalid_inet_set_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_not_list() -> + [{doc,"Test handling of invalid type in setopts"}]. + +invalid_inet_set_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_improper_list() -> + [{doc,"Test handling of invalid tye in setopts"}]. + +invalid_inet_set_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + +get_invalid_inet_option(Socket) -> + {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), + ok. + +get_invalid_inet_option_not_list(Socket) -> + {error, {options, {socket_options, some_invalid_atom_here}}} + = ssl:getopts(Socket, some_invalid_atom_here), + ok. + +get_invalid_inet_option_improper_list(Socket) -> + {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), + ok. + +set_invalid_inet_option(Socket) -> + {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), + {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), + {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), + {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), + ok. + +set_invalid_inet_option_not_list(Socket) -> + {error, {options, {not_a_proplist, some_invalid_atom_here}}} + = ssl:setopts(Socket, some_invalid_atom_here), + ok. + +set_invalid_inet_option_improper_list(Socket) -> + {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = + ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), + ok. + +wait_for_send(Socket) -> + %% Make sure TLS process processed send message event + _ = ssl:connection_information(Socket). + diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index c791f438d0..5dd5fc45af 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -218,6 +218,55 @@ start_server_transport_control(Args) -> 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), @@ -233,6 +282,34 @@ transport_accept_abuse(Opts) -> _ = 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), @@ -571,8 +648,7 @@ cert_options(Config) -> "badcert.pem"]), BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), "badkey.pem"]), - PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, - + [{client_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]}, @@ -586,30 +662,6 @@ cert_options(Config) -> {ssl_imp, new}]}, {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, - {client_psk, [{ssl_imp, new}, - {psk_identity, "Test-User"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_hint, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon_hint, [{ssl_imp, new},{reuseaddr, true}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {client_srp, [{ssl_imp, new}, - {srp_identity, {"Test-User", "secret"}}]}, - {server_srp, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_suites()}]}, - {server_srp_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_anon_suites()}]}, {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, @@ -650,15 +702,32 @@ make_dsa_cert(Config) -> [{server_dsa_opts, ServerConf}, {server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]}, - {client_dsa_opts, ClientConf}, - {server_srp_dsa, [{user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_dss_suites()} | ServerConf]}, - {client_srp_dsa, [{srp_identity, {"Test-User", "secret"}} - | ClientConf]} + {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()), @@ -1156,6 +1225,52 @@ basic_test(COpts, SOpts, 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), @@ -1197,15 +1312,22 @@ start_basic_client(openssl, Version, Port, ClientOpts) -> OpenSslPort. start_client(openssl, Port, ClientOpts, Config) -> - Cert = proplists:get_value(certfile, ClientOpts), - Key = proplists:get_value(keyfile, ClientOpts), - CA = proplists:get_value(cacertfile, ClientOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", Cert, "-CAfile", CA, - "-key", Key, "-host","localhost", "-msg", "-debug"], + 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"), @@ -1257,24 +1379,54 @@ start_server(openssl, ClientOpts, ServerOpts, Config) -> Port = inet_port(node()), Version = protocol_version(Config), Exe = "openssl", - CertArgs = openssl_cert_options(ServerOpts), - [Cipher|_] = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), - Args = ["s_server", "-accept", integer_to_list(Port), "-cipher", - ssl_cipher_format:suite_map_to_openssl_str(Cipher), - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"], + 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} | ServerOpts]}]), + {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), @@ -1329,23 +1481,31 @@ stop(Client, Server) -> close(Client). -openssl_cert_options(ServerOpts) -> - Cert = proplists:get_value(certfile, ServerOpts, undefined), - Key = proplists:get_value(keyfile, ServerOpts, undefined), - CA = proplists:get_value(cacertfile, ServerOpts, undefined), +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) ++ ["-verify", "2"] + 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) -> @@ -2005,12 +2165,68 @@ check_sane_openssl_version(Version) -> 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. @@ -2060,8 +2276,8 @@ filter_suites(Ciphers0, AtomVersion) -> ++ ssl_cipher:anonymous_suites(Version) ++ ssl_cipher:psk_suites(Version) ++ ssl_cipher:psk_suites_anon(Version) - ++ ssl_cipher:srp_suites() - ++ ssl_cipher:srp_suites_anon() + ++ 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], @@ -2178,6 +2394,14 @@ protocol_version(Config, atom) -> 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), @@ -2504,3 +2728,70 @@ kill_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. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl deleted file mode 100644 index 31d9af5298..0000000000 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ /dev/null @@ -1,2021 +0,0 @@ -%% -%% %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_to_openssl_SUITE). - -%% Note: This directive should only be used in test suites. --compile(export_all). - --include_lib("common_test/include/ct.hrl"). - --define(SLEEP, 1000). --define(OPENSSL_RENEGOTIATE, "R\n"). --define(OPENSSL_QUIT, "Q\n"). --define(OPENSSL_GARBAGE, "P\n"). --define(EXPIRE, 10). - -%%-------------------------------------------------------------------- -%% Common Test interface functions ----------------------------------- -%%-------------------------------------------------------------------- - -all() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}]; - false -> - [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}] - end. - -groups() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'sslv3', [], all_versions_tests()}, - {'dtlsv1.2', [], dtls_all_versions_tests()}, - {'dtlsv1', [], dtls_all_versions_tests()} - ]; - false -> - [{'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'sslv3', [], all_versions_tests()} - ] - end. - -all_versions_tests() -> - [ - erlang_client_openssl_server, - erlang_server_openssl_client, - erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_renegotiate_after_client_data, - erlang_client_openssl_server_nowrap_seqnum, - erlang_server_openssl_client_nowrap_seqnum, - erlang_client_openssl_server_no_server_ca_cert, - erlang_client_openssl_server_client_cert, - erlang_server_openssl_client_client_cert, - ciphers_rsa_signed_certs, - ciphers_dsa_signed_certs, - erlang_client_bad_openssl_server, - expired_session, - ssl2_erlang_server_openssl_client - ]. - -dtls_all_versions_tests() -> - case ssl_test_lib:openssl_sane_client_cert() of - true -> - [erlang_server_openssl_client_client_cert, - erlang_client_openssl_server_no_server_ca_cert, - erlang_client_openssl_server_client_cert - | dtls_all_versions_tests_2()]; - false -> - dtls_all_versions_tests_2() - end. - -dtls_all_versions_tests_2() -> - [erlang_client_openssl_server, - erlang_server_openssl_client, - erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_nowrap_seqnum, - erlang_server_openssl_client_nowrap_seqnum, - ciphers_rsa_signed_certs, - ciphers_dsa_signed_certs - %%expired_session - ]. - -alpn_tests() -> - [erlang_client_alpn_openssl_server_alpn, - erlang_server_alpn_openssl_client_alpn, - erlang_client_alpn_openssl_server, - erlang_client_openssl_server_alpn, - erlang_server_alpn_openssl_client, - erlang_server_openssl_client_alpn, - erlang_client_alpn_openssl_server_alpn_renegotiate, - erlang_server_alpn_openssl_client_alpn_renegotiate, - erlang_client_alpn_npn_openssl_server_alpn_npn, - erlang_server_alpn_npn_openssl_client_alpn_npn]. - -npn_tests() -> - [erlang_client_openssl_server_npn, - erlang_server_openssl_client_npn, - erlang_server_openssl_client_npn_renegotiate, - erlang_client_openssl_server_npn_renegotiate, - erlang_server_openssl_client_npn_only_client, - erlang_server_openssl_client_npn_only_server, - erlang_client_openssl_server_npn_only_client, - erlang_client_openssl_server_npn_only_server]. - -sni_server_tests() -> - [erlang_server_openssl_client_sni_match, - erlang_server_openssl_client_sni_match_fun, - erlang_server_openssl_client_sni_no_match, - erlang_server_openssl_client_sni_no_match_fun, - erlang_server_openssl_client_sni_no_header, - erlang_server_openssl_client_sni_no_header_fun]. - - -init_per_suite(Config0) -> - case os:find_executable("openssl") of - false -> - {skip, "Openssl not found"}; - _ -> - ct:pal("Version: ~p", [os:cmd("openssl version")]), - catch crypto:stop(), - try crypto:start() of - ok -> - ssl_test_lib:clean_start(), - Config = - case ssl_test_lib:openssl_dsa_support() of - true -> - Config1 = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:make_dsa_cert(Config1); - false -> - ssl_test_lib:make_rsa_cert(Config0) - end, - ssl_test_lib:cipher_restriction(Config) - catch _:_ -> - {skip, "Crypto did not start"} - end - end. - -end_per_suite(_Config) -> - ssl:stop(), - application:stop(crypto), - ssl_test_lib:kill_openssl(). - -init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - ssl:start(), - Config - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -init_per_testcase(expired_session, Config) -> - ct:timetrap(?EXPIRE * 1000 * 5), - ssl:stop(), - application:load(ssl), - application:set_env(ssl, session_lifetime, ?EXPIRE), - ssl:start(), - Config; - -init_per_testcase(TestCase, Config) when - TestCase == ciphers_dsa_signed_certs; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert -> - case ssl_test_lib:openssl_dsa_support() andalso ssl_test_lib:is_sane_oppenssl_client() of - true -> - special_init(TestCase, Config); - false -> - {skip, "DSA not supported by OpenSSL"} - end; -init_per_testcase(TestCase, Config) -> - ct:timetrap({seconds, 35}), - special_init(TestCase, Config). - -special_init(TestCase, Config) when - TestCase == ciphers_rsa_signed_certs; - TestCase == ciphers_dsa_signed_certs-> - ct:timetrap({seconds, 90}), - Config; -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_renegotiate; - TestCase == erlang_client_openssl_server_nowrap_seqnum; - TestCase == erlang_server_openssl_client_nowrap_seqnum; - TestCase == erlang_client_openssl_server_renegotiate_after_client_data - -> - {ok, Version} = application:get_env(ssl, protocol_version), - check_sane_openssl_renegotaite(Config, Version); - -special_init(ssl2_erlang_server_openssl_client, Config) -> - case ssl_test_lib:supports_ssl_tls_version(sslv2) of - true -> - Config; - false -> - {skip, "sslv2 not supported by openssl"} - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client_alpn; - TestCase == erlang_client_alpn_openssl_server; - TestCase == erlang_client_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client; - TestCase == erlang_server_openssl_client_alpn -> - check_openssl_alpn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; - TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_alpn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; - TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> - case check_openssl_alpn_support(Config) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_npn; - TestCase == erlang_server_openssl_client_npn; - TestCase == erlang_server_openssl_client_npn_only_server; - TestCase == erlang_server_openssl_client_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_server -> - check_openssl_npn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_server_openssl_client_npn_renegotiate; - TestCase == erlang_client_openssl_server_npn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config0) - when TestCase == erlang_server_openssl_client_sni_match; - TestCase == erlang_server_openssl_client_sni_no_match; - TestCase == erlang_server_openssl_client_sni_no_header; - TestCase == erlang_server_openssl_client_sni_match_fun; - TestCase == erlang_server_openssl_client_sni_no_match_fun; - TestCase == erlang_server_openssl_client_sni_no_header_fun -> - RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config0), - Config = [{sni_server_opts, [{sni_hosts, - [{"a.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]}, - {"b.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]} - ]}]} | Config0], - check_openssl_sni_support(Config); -special_init(TestCase, Config) - when TestCase == erlang_server_openssl_client; - TestCase == erlang_server_openssl_client_client_cert; - TestCase == erlang_server_openssl_client_reuse_session -> - case ssl_test_lib:is_sane_oppenssl_client() of - true -> - Config; - false -> - {skip, "Broken OpenSSL client"} - end; -special_init(_, Config) -> - Config. - -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - Config; -end_per_testcase(_, Config) -> - Config. - -%%-------------------------------------------------------------------- -%% Test Cases -------------------------------------------------------- -%%-------------------------------------------------------------------- - -erlang_client_openssl_server() -> - [{doc,"Test erlang client with openssl server"}]. -erlang_client_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - 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, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -erlang_client_openssl_server_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {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 = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2", "-msg"], - - 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, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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), - ok. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile, "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - %%-------------------------------------------------------------------- -erlang_client_openssl_server_anon() -> - [{doc,"Test erlang client with openssl server, anonymous"}]. -erlang_client_openssl_server_anon(Config) when is_list(Config) -> - process_flag(trap_exit, true), - %% OpenSSL expects a certificate and key, even if the cipher spec - %% is restructed to aNULL, so we use 'server_rsa_opts' here - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_anon_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, - "-cipher", "aNULL", "-msg"], - - 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, - erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ClientOpts]}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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) - end. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon() -> - [{doc,"Test erlang server with openssl client, anonymous"}]. -erlang_server_openssl_client_anon(Config) when is_list(Config) -> - - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_anon_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon_with_cert() -> - [{doc,"Test erlang server with openssl client, anonymous (with cert)"}]. -erlang_server_openssl_client_anon_with_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - - %%-------------------------------------------------------------------- -erlang_server_openssl_client_reuse_session() -> - [{doc, "Test erlang server with openssl client that reconnects with the" - "same session id, to test reusing of sessions."}]. -erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {reconnect_times, 5}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) - ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-reconnect"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_renegotiate() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. -erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - 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, - delayed_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% 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), - ok. -%%-------------------------------------------------------------------- -erlang_client_openssl_server_renegotiate_after_client_data() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. -erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - 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 = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - 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, - send_wait_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% 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), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl\n", - N = 10, - - Port = ssl_test_lib:inet_port(node()), - 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 = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - 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, {ssl_test_lib, - trigger_renegotiate, [[ErlData, N+2]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - - %% 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). -%%-------------------------------------------------------------------- -erlang_server_openssl_client_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - N = 10, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client","-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_no_server_ca_cert() -> - [{doc, "Test erlang client when openssl server sends a cert chain not" - "including the ca cert. Explicitly test this even if it is" - "implicitly tested eleswhere."}]. -erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, "-msg"], - - 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, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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). - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_client_cert() -> - [{doc,"Test erlang client with openssl server when client sends cert"}]. -erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2"], - - 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, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% 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). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_client_cert() -> - [{doc,"Test erlang server with openssl client when client sends cert"}]. -erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile,"-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Server), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_erlang_client_client_cert() -> - [{doc,"Test erlang server with erlang client when client sends cert"}]. -erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - {mfa, {ssl, send, [Data]}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -ciphers_rsa_signed_certs() -> - [{doc,"Test cipher suites that uses rsa certs"}]. -ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - Ciphers = ssl_test_lib:rsa_suites(openssl), - run_suites(Ciphers, Version, Config, rsa). -%%-------------------------------------------------------------------- - -ciphers_dsa_signed_certs() -> - [{doc,"Test cipher suites that uses dsa certs"}]. -ciphers_dsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:dsa_suites(NVersion), - run_suites(Ciphers, Version, Config, dsa). - -%%-------------------------------------------------------------------- -erlang_client_bad_openssl_server() -> - [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. -erlang_client_bad_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, server_sent_garbage, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Send garbage - true = port_command(OpensslPort, ?OPENSSL_GARBAGE), - - ct:sleep(?SLEEP), - - Client0 ! server_sent_garbage, - - ssl_test_lib:check_result(Client0, true), - - ssl_test_lib:close(Client0), - - %% Make sure openssl does not hang and leave zombie process - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client1), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -expired_session() -> - [{doc, "Test our ssl client handling of expired sessions. Will make" - "better code coverage of the ssl_manager module"}]. -expired_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-cert", CertFile,"-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client0), - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client1), - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE+1) * 1000), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client2), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -ssl2_erlang_server_openssl_client() -> - [{doc,"Test that ssl v2 clients are rejected"}]. - -ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - "-ssl2", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), - ssl_test_lib:consume_port_exit(OpenSslPort), - ssl_test_lib:check_server_alert(Server, unexpected_message), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_alpn_openssl_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{alpn_advertised_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_alpn_openssl_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [{alpn_preferred_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn() -> - [{doc,"Test erlang client with openssl server doing npn negotiation"}]. - -erlang_client_openssl_server_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn_renegotiate() -> - [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. - -erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Client, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn() -> - [{doc,"Test erlang server with openssl client and npn negotiation"}]. - -erlang_server_openssl_client_npn(Config) when is_list(Config) -> - - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_renegotiate() -> - [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. - -erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, [], - ["-nextprotoneg", "spdy/2"], Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{client_preferred_next_protocols, - {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%-------------------------------------------------------------------------- -erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). - - -%%-------------------------------------------------------------------- -%% Internal functions ------------------------------------------------ -%%-------------------------------------------------------------------- -run_suites(Ciphers, Version, Config, Type) -> - {ClientOpts, ServerOpts} = - case Type of - rsa -> - {ssl_test_lib:ssl_options(client_rsa_opts, Config), - ssl_test_lib:ssl_options(server_rsa_opts, Config)}; - dsa -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_verify_opts, Config)} - end, - - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -client_read_check([], _Data) -> - ok; -client_read_check([Hd | T], Data) -> - case binary:match(Data, list_to_binary(Hd)) of - nomatch -> - nomatch; - _ -> - client_read_check(T, Data) - end. -client_check_result(Port, DataExpected, DataReceived) -> - receive - {Port, {data, TheData}} -> - Data = list_to_binary(TheData), - NewData = <<DataReceived/binary, Data/binary>>, - ct:log("New Data: ~p", [NewData]), - case client_read_check(DataExpected, NewData) of - ok -> - ok; - _ -> - client_check_result(Port, DataExpected, NewData) - end - after 20000 -> - ct:fail({"Time out on openSSL Client", {expected, DataExpected}, - {got, DataReceived}}) - end. -client_check_result(Port, DataExpected) -> - client_check_result(Port, DataExpected, <<"">>). - -send_and_hostname(SSLSocket) -> - ssl:send(SSLSocket, "OK"), - case ssl:connection_information(SSLSocket, [sni_hostname]) of - {ok, []} -> - undefined; - {ok, [{sni_hostname, Hostname}]} -> - Hostname - end. - -erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - Version = ssl_test_lib:protocol_version(Config), - ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(Version, Hostname,Port); - _ -> - openssl_client_args(Version, Hostname, Port, SNIHostname) - end, - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server), - ok. - - -erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - Version = ssl_test_lib:protocol_version(Config), - ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), - SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, - ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(Version, Hostname,Port); - _ -> - openssl_client_args(Version, Hostname, Port, SNIHostname) - end, - - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server). - - -cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> - process_flag(trap_exit, true), - ct:log("Testing CipherSuite ~p~n", [CipherSuite]), - {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - ConnectionInfo = {ok, {Version, CipherSuite}}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - true = port_command(OpenSslPort, "Hello\n"), - - receive - {Port, {data, _}} when is_port(Port) -> - ok - after 500 -> - ct:log("Time out on openssl port, check that" - " the messages Hello and world are received" - " during close of port" , []), - ok - end, - - true = port_command(OpenSslPort, " world\n"), - - Result = ssl_test_lib:wait_for_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Client), - - Return = case Result of - ok -> - []; - Error -> - [{CipherSuite, Error}] - end, - process_flag(trap_exit, false), - Return. - -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, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - 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 = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - 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, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - - 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). - -start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, - {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", - "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - 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, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, - {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", - "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - 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). - -start_erlang_client_and_openssl_server_for_npn_negotiation(Config, 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 = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | 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 = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - 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, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-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). - - -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, erlang_ssl_receive, [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). - - -erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> - {ok, Protocol} = ssl:negotiated_protocol(Socket), - erlang_ssl_receive(Socket, Data), - {ok, Protocol} = ssl:negotiated_protocol(Socket), - ok. - -erlang_ssl_receive(Socket, Data) -> - ct:log("Connection info: ~p~n", - [ssl:connection_information(Socket)]), - receive - {ssl, Socket, "R\n"} -> - %% Swallow s_client renegotiation command. - %% openssl s_client connected commands can appear on - %% server side with some openssl versions. - erlang_ssl_receive(Socket,Data); - {ssl, Socket, Data} -> - io:format("Received ~p~n",[Data]), - %% open_ssl server sometimes hangs waiting in blocking read - ssl:send(Socket, "Got it"), - ok; - {ssl, Socket, Byte} when length(Byte) == 1 -> - erlang_ssl_receive(Socket, tl(Data)); - {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), - erlang_ssl_receive(Socket,Data); - Other -> - ct:fail({unexpected_message, Other}) - end. - -connection_info(Socket, Version) -> - case ssl:connection_information(Socket, [version]) of - {ok, [{version, Version}] = Info} -> - ct:log("Connection info: ~p~n", [Info]), - ok; - {ok, [{version, OtherVersion}]} -> - {wrong_version, OtherVersion} - end. - -connection_info_result(Socket) -> - ssl:connection_information(Socket). - - -delayed_send(Socket, [ErlData, OpenSslData]) -> - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -server_sent_garbage(Socket) -> - receive - server_sent_garbage -> - {error, closed} == ssl:send(Socket, "data") - - end. - -send_wait_send(Socket, [ErlData, OpenSslData]) -> - ssl:send(Socket, ErlData), - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -check_openssl_sni_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case ssl_test_lib:is_sane_oppenssl_client() of - true -> - case string:str(HelpText, "-servername") of - 0 -> - {skip, "Current openssl doesn't support SNI"}; - _ -> - Config - end; - false -> - {skip, "Current openssl doesn't support SNI or extension handling is flawed"} - 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. - -check_openssl_alpn_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "alpn") of - 0 -> - {skip, "Openssl not compiled with alpn support"}; - _ -> - Config - end. - -check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; - Version == 'tlsv1.2' -> - case os:cmd("openssl version") of - "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, _) -> - 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. - -openssl_client_args(Version, Hostname, Port) -> - ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. - -openssl_client_args(Version, Hostname, Port, ServerName) -> - ["s_client", "-connect", Hostname ++ ":" ++ - integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. - - -hostname_format(Hostname) -> - case lists:member($., Hostname) of - true -> - Hostname; - false -> - "localhost" - end. - - -openssl_has_common_ciphers(Ciphers) -> - OCiphers = ssl_test_lib:common_ciphers(openssl), - has_common_ciphers(Ciphers, OCiphers). - -has_common_ciphers([], _) -> - false; -has_common_ciphers([Cipher | Rest], OCiphers) -> - case lists:member(Cipher, OCiphers) of - true -> - true; - _ -> - has_common_ciphers(Rest, OCiphers) - end. diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl new file mode 100644 index 0000000000..5df8853b1b --- /dev/null +++ b/lib/ssl/test/tls_1_3_record_SUITE.erl @@ -0,0 +1,973 @@ +%% +%% %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(tls_1_3_record_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). + +all() -> + [encode_decode, + finished_verify_data, + '1_RTT_handshake']. + +init_per_suite(Config) -> + catch crypto:stop(), + try (ok == crypto:start()) andalso ssl_test_lib:sufficient_crypto_support('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, "Not enough crypto support for TLS-1.3"} + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +encode_decode() -> + [{doc,"Test TLS 1.3 record encode/decode functions"}]. + +encode_decode(_Config) -> + ConnectionStates = + #{current_read => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}, + current_write => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}}, + + PlainText = [11, + <<0,2,175>>, + <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, + 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, + 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, + 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, + 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, + 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, + 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, + 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, + 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, + 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, + 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, + 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, + 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, + 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, + 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, + 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, + 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, + 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, + 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, + 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, + 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, + 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, + 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, + 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, + 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, + 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, + 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, + 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, + 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, + 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, + 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, + 146,152,146,151,107,126,216,210,9,93,0,0>>], + + {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), + CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, + + {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = + tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), + + DecodedText = iolist_to_binary(PlainText), + ct:log("Decoded: ~p ~n", [DecodedText]), + ok. +%%-------------------------------------------------------------------- +'1_RTT_handshake'() -> + [{doc,"Test TLS 1.3 1-RTT Handshake"}]. + +'1_RTT_handshake'(_Config) -> + %% ConnectionStates with NULL cipher + ConnStatesNull = + #{current_write => + #{security_parameters => + #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, + sequence_number => 0 + } + }, + + %% {client} construct a ClientHello handshake message: + %% + %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% {client} send handshake record: + %% + %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba + %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 + %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 + %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 + %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 + %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d + %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af + %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 + %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 + %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb + %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + ClientHello = + hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + ClientHelloRecord = + %% Current implementation always sets + %% legacy_record_version to Ox0303 + hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb + 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + {CHEncrypted, _} = + tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull), + ClientHelloRecord = iolist_to_binary(CHEncrypted), + + %% {server} extract secret "early": + %% + %% salt: 0 (all zero octets) + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + HKDFAlgo = sha256, + Salt = binary:copy(<<?BYTE(0)>>, 32), + IKM = binary:copy(<<?BYTE(0)>>, 32), + EarlySecret = + hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"), + + {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}), + + %% {client} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 + %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 + %% + %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c + CPublicKey = + hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"), + + %% {server} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e + %% + %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f + SPrivateKey = + hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"), + + SPublicKey = + hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"), + + %% {server} construct a ServerHello handshake message: + %% + %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 + %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + ServerHello = + hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60 + dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + %% {server} derive secret for handshake "tls13 derived": + %% + %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 + %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + Hash = + hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Hash = crypto:hash(HKDFAlgo, <<>>), + + Info = + hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)), + + Expanded = + hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"), + + Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "handshake": + %% + %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 + %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + %% + %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d + %% + %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + + %% salt = Expanded + HandshakeIKM = + hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"), + + HandshakeSecret = + hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"), + + HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519), + + {handshake_secret, HandshakeSecret} = + tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM, + {early_secret, EarlySecret}), + + %% {server} derive secret "tls13 c hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + + %% PRK = HandshakeSecret + CHSTHash = + hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTrafficSecret = + hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"), + + CHSH = <<ClientHello/binary,ServerHello/binary>>, + CHSTHash = crypto:hash(HKDFAlgo, CHSH), + CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CHSTrafficSecret = + tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + %% {server} derive secret "tls13 s hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + + %% PRK = HandshakeSecret + %% hash = CHSTHash + SHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + SHSTrafficSecret = + hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"), + + SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SHSTrafficSecret = + tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + + %% {server} derive secret for master "tls13 derived": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + + %% PRK = HandshakeSecret + %% hash = Hash + %% info = Info + MasterDeriveSecret = + hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"), + + MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "master": + %% + %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 + %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + + %% salt = MasterDeriveSecret + %% IKM = IKM + MasterSecret = + hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"), + + {master_secret, MasterSecret} = + tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}), + + %% {server} send handshake record: + %% + %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e + %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 + %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 + %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 + %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + %% + %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6 + %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + + %% payload = ServerHello + ServerHelloRecord = + hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6 + af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + {SHEncrypted, _} = + tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull), + ServerHelloRecord = iolist_to_binary(SHEncrypted), + + %% {server} derive write traffic keys for handshake data: + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e + %% e4 03 bc + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30 + + %% PRK = SHSTrafficSecret + WriteKeyInfo = + hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"), + + WriteKey = + hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"), + + WriteIVInfo = + hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"), + + WriteIV = + hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"), + + Cipher = aes_128_gcm, %% TODO: get from ServerHello + + WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)), + %% TODO: remove hardcoded IV size + WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12), + + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret), + + %% {server} construct an EncryptedExtensions handshake message: + %% + %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + %% 00 02 40 01 00 00 00 00 + %% + %% {server} construct a Certificate handshake message: + %% + %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00 + %% + %% {server} construct a CertificateVerify handshake message: + %% + %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c + %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 + EncryptedExtensions = + hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + 00 02 40 01 00 00 00 00"), + + Certificate = + hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + 96 12 29 ac 91 87 b4 2b 4d e1 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c + 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"), + + %% {server} calculate finished "tls13 finished": + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% hash (0 octets): (empty) + %% + %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + %% 64 00 + %% + %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8 + %% + %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 + + %% PRK = SHSTrafficSecret + FInfo = + hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + 64 00"), + + FExpanded = + hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"), + + FinishedVerifyData = + hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"), + + FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)), + + FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo), + + MessageHistory0 = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0), + + %% {server} construct a Finished handshake message: + %% + %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + %% 18 + FinishedHSBin = + hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + 18"), + + FinishedHS = #finished{verify_data = FinishedVerifyData}, + + FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}), + FinishedHSBin = iolist_to_binary(FinishedIOList), + + %% {server} derive secret "tls13 c ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 + + %% PRK = MasterSecret + CAPTHash = + hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + CAPTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + CAPTrafficSecret = + hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"), + + CHSF = <<ClientHello/binary, + ServerHello/binary, + EncryptedExtensions/binary, + Certificate/binary, + CertificateVerify/binary, + FinishedHSBin/binary>>, + + CAPTHash = crypto:hash(HKDFAlgo, CHSF), + + CAPTInfo = + tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CAPTrafficSecret = + tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 s ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + + %% PRK = MasterSecret + %% hash = CAPTHash + SAPTInfo = + hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + SAPTrafficSecret = + hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"), + + SAPTInfo = + tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SAPTrafficSecret = + tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 exp master": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 + + %% PRK = MasterSecret + %% hash = CAPTHash + ExporterInfo = + hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + ExporterMasterSecret = + hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"), + + ExporterInfo = + tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + ExporterMasterSecret = + tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive write traffic keys for application data: + %% + %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 + %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac + %% 92 e3 56 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84 + + %% PRK = SAPTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SWKey = + hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"), + + SWIV = + hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"), + + {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret), + + %% {server} derive read traffic keys for handshake data: + %% + %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f + %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 + %% 25 8d 01 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f + + %% PRK = CHSTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SRKey = + hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"), + + SRIV = + hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"), + + {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret). + +%%-------------------------------------------------------------------- +finished_verify_data() -> + [{doc,"Test TLS 1.3 Finished message handling"}]. + +finished_verify_data(_Config) -> + ClientHello = + hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09 + 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 + 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01 + 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d + 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 + 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00 + 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06 + 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72 + d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed + 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00 + 02 01 01 00 2b 00 03 02 03 04"), + + ServerHello = + hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79 + 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 + 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24 + 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b + 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 + 28 80 b6 15 00 2b 00 02 03 04"), + + EncryptedExtensions = + hexstr2bin("08 00 00 02 00 00"), + + Certificate = + hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30 + 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 + 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 + 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 + 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 + 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 + 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 + 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 + 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 + 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e + 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d + 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 + 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 + b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 + 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d + bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d + f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 + f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b + 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 + f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 + ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 + ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 + 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb + 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 + 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 + d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad + 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 + 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 + ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 + 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 + 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 + 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 + 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 + cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 + 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 + 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b + fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 + 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd + 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 + f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 + 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 + 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 + 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 + ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e + 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 + c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 + cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 + 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f + 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 + 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 + 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0 + 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d + 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75 + 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8 + 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13 + 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73 + df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b + 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2 + 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84 + fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c + 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5 + 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79 + ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a + aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7 + 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b + 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36 + c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe + c7 e4 a7 30 f1 36 9f 9e"), + + BaseKey = + hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4 + 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"), + + VerifyData = + hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea + c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"), + + Messages = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedKey = tls_v1:finished_key(BaseKey, sha256), + VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +hexstr2int(S) -> + B = hexstr2bin(S), + Bits = size(B) * 8, + <<Integer:Bits/integer>> = B, + Integer. + +hexstr2bin(S) when is_binary(S) -> + hexstr2bin(S, <<>>); +hexstr2bin(S) -> + hexstr2bin(list_to_binary(S), <<>>). +%% +hexstr2bin(<<>>, Acc) -> + Acc; +hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE + C =:= 10; %% LF + C =:= 13 -> %% CR + hexstr2bin(T, Acc); +hexstr2bin(<<X,Y,T/binary>>, Acc) -> + I = hex2int(X) * 16 + hex2int(Y), + hexstr2bin(T, <<Acc/binary,I>>). + +hex2int(C) when $0 =< C, C =< $9 -> + C - $0; +hex2int(C) when $A =< C, C =< $F -> + C - $A + 10; +hex2int(C) when $a =< C, C =< $f -> + C - $a + 10. diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..f0b224d4e5 --- /dev/null +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -0,0 +1,153 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(tls_1_3_version_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + [{client_type, erlang}, {server_type, erlang} | + Config] + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls13_client_with_ext_tls12_server() -> + [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " + "client has TLS 1.3 specsific extensions"}]. + +tls13_client_with_ext_tls12_server(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + ServerOpts = [{versions, ['tlsv1.2']}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {signature_algs_cert, [ecdsa_secp384r1_sha384, + ecdsa_secp256r1_sha256, + rsa_pss_rsae_sha256, + rsa_pkcs1_sha256, + {sha256,rsa},{sha256,dsa}]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl new file mode 100644 index 0000000000..5a74ec1892 --- /dev/null +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -0,0 +1,880 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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(tls_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_record.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). + +-define(SLEEP, 500). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], api_tests() -- [sockname]}, + {'tlsv1.2', [], api_tests()}, + {'tlsv1.1', [], api_tests()}, + {'tlsv1', [], api_tests()}, + {'sslv3', [], api_tests() ++ [ssl3_cipher_suite_limitation]} + ]. + +api_tests() -> + [ + tls_upgrade, + tls_upgrade_with_timeout, + tls_downgrade, + tls_shutdown, + tls_shutdown_write, + tls_shutdown_both, + tls_shutdown_error, + tls_client_closes_socket, + tls_closed_in_active_once, + tls_tcp_msg, + tls_tcp_msg_big, + tls_dont_crash_on_handshake_garbage, + tls_tcp_error_propagation_in_active_mode, + peername, + sockname, + tls_server_handshake_timeout, + transport_close, + emulated_options, + accept_pool, + reuseaddr + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +tls_upgrade() -> + [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. + +tls_upgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, [binary]}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_upgrade_with_timeout() -> + [{doc,"Test ssl_accept/3"}]. + +tls_upgrade_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, TcpOpts}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_downgrade() -> + [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. +tls_downgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ready, Client, ready), + + Server ! go, + Client ! go, + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +tls_shutdown() -> + [{doc,"Test API function ssl:shutdown/2"}]. +tls_shutdown(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_result, [server]}}, + {options, [{exit_on_close, false}, + {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, + {?MODULE, tls_shutdown_result, [client]}}, + {options, + [{exit_on_close, false}, + {active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_shutdown_write() -> + [{doc,"Test API function ssl:shutdown/2 with option write."}]. +tls_shutdown_write(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_both() -> + [{doc,"Test API function ssl:shutdown/2 with option both."}]. +tls_shutdown_both(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_error() -> + [{doc,"Test ssl:shutdown/2 error handling"}]. +tls_shutdown_error(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + {ok, Listen} = ssl:listen(Port, ServerOpts), + {error, enotconn} = ssl:shutdown(Listen, read_write), + ok = ssl:close(Listen), + {error, closed} = ssl:shutdown(Listen, read_write). +%%-------------------------------------------------------------------- +tls_client_closes_socket() -> + [{doc,"Test what happens when client closes socket before handshake is compleated"}]. + +tls_client_closes_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Connect = fun() -> + {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname, Port, [binary]]), + %% Make sure that ssl_accept is called before + %% client process ends and closes socket. + ct:sleep(?SLEEP) + end, + + _Client = spawn_link(Connect), + + ssl_test_lib:check_result(Server, {error,closed}). + +%%-------------------------------------------------------------------- +tls_closed_in_active_once() -> + [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. + +tls_closed_in_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + Port = ssl_test_lib:inet_port(node()), + Server = fun() -> + {ok, Listen} = gen_tcp:listen(Port, TcpOpts), + {ok, TcpServerSocket} = gen_tcp:accept(Listen), + {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts), + lists:foreach( + fun(_) -> + ssl:send(ServerSocket, "some random message\r\n") + end, lists:seq(1, 20)), + %% Close TCP instead of SSL socket to trigger the bug: + gen_tcp:close(TcpServerSocket), + gen_tcp:close(Listen) + end, + spawn_link(Server), + {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), + Result = tls_closed_in_active_once_loop(Socket), + ssl:close(Socket), + case Result of + ok -> ok; + _ -> ct:fail(Result) + end. +%%-------------------------------------------------------------------- +tls_tcp_msg() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. + +tls_tcp_msg(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}, {active, false}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]) + end + end. +%%-------------------------------------------------------------------- +tls_tcp_msg_big() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. + +tls_tcp_msg_big(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + + gen_tcp:send(Socket, <<?BYTE(0), + ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, timeout}} -> + ct:fail("hangs"); + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]); + {'EXIT', Server, _} -> + ok + end + end. + +%%-------------------------------------------------------------------- +tls_dont_crash_on_handshake_garbage() -> + [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " + "instead of crashing and exposing state to user code"}]. + +tls_dont_crash_on_handshake_garbage(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + unlink(Server), monitor(process, Server), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + + % Send hello and garbage record + ok = gen_tcp:send(Socket, + [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values + + <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage + ]), + % Send unexpected change_cipher_spec + ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), + + % Ensure we receive an alert, not sudden disconnect + {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). + +%%-------------------------------------------------------------------- +tls_tcp_error_propagation_in_active_mode() -> + [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. +tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, receive_msg, []}}, + {options, ClientOpts}]), + + {status, _, _, StatusInfo} = sys:get_status(Pid), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + StaticEnv = element(2, State), + Socket = element(11, StaticEnv), + %% Fake tcp error + Pid ! {tcp_error, Socket, etimedout}, + + ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). + +%%-------------------------------------------------------------------- +peername() -> + [{doc,"Test API function peername/1"}]. + +peername(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peername_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peername_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ClientIp, ClientPort}}, + ClientMsg = {ok, {ServerIp, Port}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +sockname() -> + [{doc,"Test API function sockname/1"}]. +sockname(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sockname_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sockname_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ServerIp, Port}}, + ClientMsg = {ok, {ClientIp, ClientPort}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_server_handshake_timeout() -> + [{doc,"Test server handshake timeout"}]. + +tls_server_handshake_timeout(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {ssl_test_lib, + no_result_msg, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), + + receive + {tcp_closed, CSocket} -> + ssl_test_lib:check_result(Server, {error, timeout}), + receive + {'EXIT', Server, _} -> + %% Make sure supervisor had time to react on process exit + %% Could we come up with a better solution to this? + ct:sleep(500), + [] = supervisor:which_children(tls_connection_sup) + end + end. +transport_close() -> + [{doc, "Test what happens if socket is closed on TCP level after a while of normal operation"}]. +transport_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname,Port,[binary, {active, false}]]), + {ok, SslS} = rpc:call(ClientNode, ssl, connect, + [TcpS,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), self(), Server]), + ok = ssl:send(SslS, "Hello world"), + {ok,<<"Hello world">>} = ssl:recv(SslS, 11), + gen_tcp:close(TcpS), + {error, _} = ssl:send(SslS, "Hello world"). + +%%-------------------------------------------------------------------- +ssl3_cipher_suite_limitation() -> + [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. +ssl3_cipher_suite_limitation(Config) when is_list(Config) -> + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + ok = gen_tcp:send(Socket, + <<22, 3,0, 49:16, % handshake, SSL 3.0, length + 1, 45:24, % client_hello, length + 3,0, % SSL 3.0 + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, % no session ID + %% three cipher suites -- null, one with sha256 hash and one with sha hash + 6:16, 0,255, 0,61, 0,57, + 1, 0 % no compression + >>), + {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), + {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), + ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), + case ServerHello of + #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> + ok; + _ -> + ct:fail({unexpected_server_hello, ServerHello}) + end. +%%-------------------------------------------------------------------- +emulated_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +emulated_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {packet, 0}, {header, 0}, + {active, true}], + %% Shall be the reverse order of Values! + Options = [active, header, packet, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). +accept_pool() -> + [{doc,"Test having an accept pool."}]. +accept_pool(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {accepters, 3}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server0), + [Server1, Server2] = ssl_test_lib:accepters(2), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +%%-------------------------------------------------------------------- +reuseaddr() -> + [{doc,"Test reuseaddr option"}]. + +reuseaddr(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false}, {reuseaddr, true}| ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false} | ClientOpts]}]), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {reuseaddr, true} | ServerOpts]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +upgrade_result(Socket) -> + ssl:setopts(Socket, [{active, true}]), + ok = ssl:send(Socket, "Hello world"), + %% Make sure binary is inherited from tcp socket and that we do + %% not get the list default! + receive + {ssl, _, <<"H">>} -> + receive + {ssl, _, <<"ello world">>} -> + ok + end; + {ssl, _, <<"Hello world">>} -> + ok + end. +tls_downgrade_result(Socket, Pid) -> + ok = ssl_test_lib:send_recv_result(Socket), + Pid ! {self(), ready}, + receive + go -> + ok + end, + case ssl:close(Socket, {self(), 10000}) of + {ok, TCPSocket} -> + inet:setopts(TCPSocket, [{active, true}]), + gen_tcp:send(TCPSocket, "Downgraded"), + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:fail("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; + {error, timeout} -> + ct:fail("Timed out, downgrade aborted"), + ok; + Fail -> + {error, Fail} + end. + +tls_shutdown_result(Socket, server) -> + ssl:send(Socket, "Hej"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej hopp"} = ssl:recv(Socket, 8), + ok; + +tls_shutdown_result(Socket, client) -> + ssl:send(Socket, "Hej hopp"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej"} = ssl:recv(Socket, 3), + ok. + +tls_shutdown_write_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, write); +tls_shutdown_write_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_shutdown_both_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, read_write); +tls_shutdown_both_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_closed_in_active_once_loop(Socket) -> + case ssl:setopts(Socket, [{active, once}]) of + ok -> + receive + {ssl, Socket, _} -> + tls_closed_in_active_once_loop(Socket); + {ssl_closed, Socket} -> + ok + after 5000 -> + no_ssl_closed_received + end; + {error, closed} -> + ok + end. + +drop_handshakes(Socket, Timeout) -> + {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), + {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), + case RecType of + 22 -> drop_handshakes(Socket, Timeout); + _ -> {ok, <<Header/binary, Frag/binary>>} + end. + +receive_msg(_) -> + receive + Msg -> + Msg + end. + +sockname_result(S) -> + ssl:sockname(S). + +peername_result(S) -> + ssl:peername(S). + +tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), + ssl:setopts(Socket, [{nodelay, true}]), + {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl index 9b2033ec4a..be8ab3b98e 100644 --- a/lib/stdlib/test/binary_module_SUITE.erl +++ b/lib/stdlib/test/binary_module_SUITE.erl @@ -716,22 +716,22 @@ referenced(Config) when is_list(Config) -> badarg = ?MASK_ERROR(binary:referenced_byte_size(apa)), badarg = ?MASK_ERROR(binary:referenced_byte_size({})), badarg = ?MASK_ERROR(binary:referenced_byte_size(1)), - A = <<1,2,3>>, - B = binary:copy(A,1000), - 3 = binary:referenced_byte_size(A), - 3000 = binary:referenced_byte_size(B), - <<_:8,C:2/binary>> = A, - 3 = binary:referenced_byte_size(C), - 2 = binary:referenced_byte_size(binary:copy(C)), - <<_:7,D:2/binary,_:1>> = A, - 2 = binary:referenced_byte_size(binary:copy(D)), - 3 = binary:referenced_byte_size(D), - <<_:8,E:2/binary,_/binary>> = B, - 3000 = binary:referenced_byte_size(E), - 2 = binary:referenced_byte_size(binary:copy(E)), - <<_:7,F:2/binary,_:1,_/binary>> = B, - 2 = binary:referenced_byte_size(binary:copy(F)), - 3000 = binary:referenced_byte_size(F), + A = <<0:(1024 * 8)>>, + B = binary:copy(A, 1000), + 1024 = binary:referenced_byte_size(A), + 1024000 = binary:referenced_byte_size(B), + <<_:8,C:1023/binary>> = A, + 1024 = binary:referenced_byte_size(C), + 1023 = binary:referenced_byte_size(binary:copy(C)), + <<_:7,D:1023/binary,_:1>> = A, + 1023 = binary:referenced_byte_size(binary:copy(D)), + 1024 = binary:referenced_byte_size(D), + <<_:8,E:128/binary,_/binary>> = B, + 1024000 = binary:referenced_byte_size(E), + 128 = binary:referenced_byte_size(binary:copy(E)), + <<_:7,F:128/binary,_:1,_/binary>> = B, + 128 = binary:referenced_byte_size(binary:copy(F)), + 1024000 = binary:referenced_byte_size(F), ok. |