diff options
-rw-r--r-- | lib/ssh/doc/src/ssh.xml | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 105 | ||||
-rw-r--r-- | lib/ssh/test/ssh_protocol_SUITE.erl | 11 | ||||
-rw-r--r-- | lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli | 3 |
4 files changed, 100 insertions, 23 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 66f872490c..1e4dd91eb6 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -462,7 +462,7 @@ kex is implicit but public_key is set explicitly.</p> </warning> </item> - <tag><c><![CDATA[{dh_gex_groups, [{Size=integer(),G=integer(),P=integer()}] | {file,filename()} }]]></c></tag> + <tag><c><![CDATA[{dh_gex_groups, [{Size=integer(),G=integer(),P=integer()}] | {file,filename()} {ssh_moduli_file,filename()} }]]></c></tag> <item> <p>Sets the groups that the server may choose among when diffie-hellman-group-exchange is negotiated. See RFC 4419 for details. @@ -471,6 +471,8 @@ kex is implicit but public_key is set explicitly.</p> </p> <p>If the parameter is <c>{file,filename()}</c>, the file must exist and have one or more three-tuples terminated by a dot. The interpretation is as if the tuples had been given directly in the option. The file is read when the daemon starts. </p> + <p>If the parameter is <c>{ssh_moduli_file,filename()}</c>, the file must exist and be in ssh-keygen moduli file format. The file is read when the daemon starts. + </p> </item> <tag><c><![CDATA[{pwdfun, fun(User::string(), password::string()) -> boolean()}]]></c></tag> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 8d36c847de..9befceb51b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -421,28 +421,59 @@ handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> handle_pref_algs(Opt); -handle_ssh_option({dh_gex_groups,L=[{I1,I2,I3}|_]}) when is_integer(I1), I1>0, - is_integer(I2), I2>0, - is_integer(I3), I3>0 -> - {dh_gex_groups, public_key:moduli_collect_per_size( - lists:map(fun({N,G,P}) when is_integer(N),N>0, - is_integer(G),G>0, - is_integer(P),P>0 -> {N,{G,P}} end, L) - )}; -handle_ssh_option({dh_gex_groups,{file,File=[C|_]}}=Opt) when is_integer(C), C>0 -> - %% A string, (file name) - case file:consult(File) of - {ok, List} -> - try handle_ssh_option({dh_gex_groups,List}) of - {dh_gex_groups,_} = NewOpt -> - NewOpt - catch - _:_ -> - throw({error, {{eoptions, Opt}, "Bad format in file"}}) - end; - Error -> - throw({error, {{eoptions, Opt},{"Error reading file",Error}}}) - end; + +handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> + {dh_gex_groups, + collect_per_size( + lists:foldl( + fun({N,G,P}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,{G,P}}, Acc) when is_integer(N),N>0, + is_integer(G),G>0, + is_integer(P),P>0 -> + [{N,{G,P}} | Acc]; + ({N,GPs}, Acc) when is_list(GPs) -> + lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, + is_integer(Pi),Pi>0 -> + [{N,{Gi,Pi}} | Acci] + end, Acc, GPs) + end, [], L0))}; + +handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, + Tag == file ; + Tag == ssh_moduli_file -> + {ok,GroupDefs} = + case Tag of + file -> + file:consult(File); + ssh_moduli_file -> + case file:open(File,[read]) of + {ok,D} -> + try + {ok,Moduli} = read_moduli_file(D, 1, []), + file:close(D), + {ok, Moduli} + catch + _:_ -> + throw({error, {{eoptions, Opt}, "Bad format in file "++File}}) + end; + {error,enoent} -> + throw({error, {{eoptions, Opt}, "File not found:"++File}}); + {error,Error} -> + throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}}) + end + end, + + try + handle_ssh_option({dh_gex_groups,GroupDefs}) + catch + _:_ -> + throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) + end; + + handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, is_integer(I), I>=Min, is_integer(Max), Max>=I -> @@ -665,3 +696,33 @@ directory_exist_readable(Dir) -> +collect_per_size(L) -> + lists:foldr( + fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; + ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] + end, [], lists:sort(L)). + +read_moduli_file(D, I, Acc) -> + case io:get_line(D,"") of + {error,Error} -> + {error,Error}; + eof -> + {ok, Acc}; + "#" ++ _ -> read_moduli_file(D, I+1, Acc); + <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); + Data -> + Line = if is_binary(Data) -> binary_to_list(Data); + is_list(Data) -> Data + end, + try + [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), + M = {list_to_integer(Size), + {list_to_integer(G), list_to_integer(P,16)} + }, + read_moduli_file(D, I+1, [M|Acc]) + catch + _:_ -> + read_moduli_file(D, I+1, Acc) + end + end. + diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl index 9d54f14ff6..0292c8d149 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -67,6 +67,7 @@ groups() -> {kex, [], [no_common_alg_server_disconnects, no_common_alg_client_disconnects, gex_client_init_option_groups, + gex_client_init_option_groups_moduli_file, gex_client_init_option_groups_file ]}, {service_requests, [], [bad_service_name, @@ -90,6 +91,7 @@ init_per_testcase(no_common_alg_server_disconnects, Config) -> start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']}]}]); init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ; + TC == gex_client_init_option_groups_moduli_file ; TC == gex_client_init_option_groups_file -> Opts = case TC of gex_client_init_option_groups -> @@ -98,6 +100,10 @@ init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ; DataDir = ?config(data_dir, Config), F = filename:join(DataDir, "dh_group_test"), [{dh_gex_groups, {file,F}}]; + gex_client_init_option_groups_moduli_file -> + DataDir = ?config(data_dir, Config), + F = filename:join(DataDir, "dh_group_test.moduli"), + [{dh_gex_groups, {ssh_moduli_file,F}}]; _ -> [] end, @@ -110,6 +116,7 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(no_common_alg_server_disconnects, Config) -> stop_std_daemon(Config); end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ; + TC == gex_client_init_option_groups_moduli_file ; TC == gex_client_init_option_groups_file -> stop_std_daemon(Config); end_per_testcase(_TestCase, Config) -> @@ -335,6 +342,10 @@ gex_client_init_option_groups_file(Config) -> do_gex_client_init(Config, {2000, 2048, 4000}, {5,61}). +gex_client_init_option_groups_moduli_file(Config) -> + do_gex_client_init(Config, {2000, 2048, 4000}, + {5,16#B7}). + do_gex_client_init(Config, {Min,N,Max}, {G,P}) -> {ok,_} = ssh_trpt_test_lib:exec( diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli new file mode 100644 index 0000000000..f6995ba4c9 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli @@ -0,0 +1,3 @@ +20151021104105 2 6 100 2222 5 B7 +20151021104106 2 6 100 1111 5 4F + |