From 519f89016e7ce755775a88730814fa34af21676c Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 19 Apr 2017 12:01:26 +0200 Subject: ssh: server-sig-algs, client side --- lib/ssh/src/ssh.hrl | 2 +- lib/ssh/src/ssh_auth.erl | 85 +++++++++++++++------------------- lib/ssh/src/ssh_connection_handler.erl | 23 +++++++-- lib/ssh/src/ssh_options.erl | 20 ++------ 4 files changed, 61 insertions(+), 69 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 1a95bb27e7..cf2a359e6c 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -38,7 +38,6 @@ -define(MAX_RND_PADDING_LEN, 15). -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']). -define(FALSE, 0). -define(TRUE, 1). @@ -201,6 +200,7 @@ userauth_quiet_mode, % boolean() userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] userauth_supported_methods, % string() eg "keyboard-interactive,password" + userauth_pubkeys, kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" userauth_preference, available_host_keys, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 51df54341f..aadd1ad6dc 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -136,34 +136,40 @@ keyboard_interactive_msg([#ssh{user = User, Ssh) end. -publickey_msg([Alg, #ssh{user = User, +publickey_msg([SigAlg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = ssh_transport:sha(Alg), + Hash = ssh_transport:sha(SigAlg), + KeyAlg = key_alg(SigAlg), {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of + case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of {ok, PrivKey} -> - StrAlgo = atom_to_list(Alg), - case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of - not_ok -> - {not_ok, Ssh}; + SigAlgStr = atom_to_list(SigAlg), + try + Key = ssh_transport:extract_public_key(PrivKey), + public_key:ssh_encode(Key, ssh2_pubkey) + of PubKeyBlob -> - SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, StrAlgo), + SigData = build_sig_data(SessionId, User, Service, + PubKeyBlob, SigAlgStr), Sig = ssh_transport:sign(SigData, Hash, PrivKey), - SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), + SigBlob = list_to_binary([?string(SigAlgStr), + ?binary(Sig)]), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "publickey", data = [?TRUE, - ?string(StrAlgo), + ?string(SigAlgStr), ?binary(PubKeyBlob), ?binary(SigBlob)]}, Ssh) - end; + catch + _:_ -> + {not_ok, Ssh} + end; _Error -> {not_ok, Ssh} end. @@ -190,8 +196,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> method = "none", data = <<>>}, Ssh#ssh{user = User, - userauth_preference = - method_preference(?GET_OPT(pref_public_key_algs, Opts)), + userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys), userauth_methods = none, service = "ssh-connection"} ) @@ -265,8 +270,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - case pre_verify_sig(User, binary_to_list(BAlg), - KeyBlob, Opts) of + case pre_verify_sig(User, KeyBlob, Opts) of true -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( @@ -446,10 +450,10 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -method_preference(PubKeyAlgs) -> +method_preference(SigKeyAlgs) -> %% PubKeyAlgs: List of user (client) public key algorithms to try to use. %% All of the acceptable algorithms is the default values. - PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- PubKeyAlgs], + PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- SigKeyAlgs], NonPKmethods = [{"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], @@ -492,9 +496,9 @@ get_password_option(Opts, User) -> false -> ?GET_OPT(password, Opts) end. -pre_verify_sig(User, Alg, KeyBlob, Opts) -> +pre_verify_sig(User, KeyBlob, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) @@ -505,21 +509,19 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> try - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), UserOpts = ?GET_OPT(user_options, Opts), - case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <> = SigWLen, - <> = AlgSig, - ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); - false -> - false - end + Key0 = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + true = KeyCb:is_auth_key(Key0, User, [{key_cb_private,KeyCbOpts}|UserOpts]), + Key0 + of + Key -> + PlainText = build_sig_data(SessionId, User, Service, + KeyBlob, Alg), + <> = SigWLen, + <> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key) catch _:_ -> false @@ -591,18 +593,7 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> language = "en"}}) end. -decode_public_key_v2(Bin, _Type) -> - try - public_key:ssh_decode(Bin, ssh2_pubkey) - of - Key -> {ok, Key} - catch - _:_ -> {error, bad_format} - end. -encode_public_key(_Alg, Key) -> - try - public_key:ssh_encode(Key, ssh2_pubkey) - catch - _:_ -> not_ok - end. +key_alg('rsa-sha2-256') -> 'ssh-rsa'; +key_alg('rsa-sha2-512') -> 'ssh-rsa'; +key_alg(Alg) -> Alg. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 128a9175f5..ac1b792f32 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -464,6 +464,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) -> true -> ssh_io; false -> ssh_no_io end, + userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts), userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), peer = {PeerName, PeerAddr} }; @@ -711,7 +712,7 @@ handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {service_request,Role}, D, [postpone]}; -handle_event(internal, Msg, {ext_info,Role,renegotiate}, D) when is_tuple(Msg) -> +handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) -> %% If something else arrives, goto next state and handle the event in that one {next_state, {connected,Role}, D, [postpone]}; @@ -1131,6 +1132,7 @@ handle_event({call,From}, stop, StateName, D0) -> {Repls,D} = send_replies(Replies, D0), {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}}; + handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> {keep_state_and_data, [postpone]}; @@ -1380,12 +1382,16 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) -> disconnect(Msg, StateName, D); +handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> + %% If something else arrives, goto next state and handle the event in that one + {next_state, {connected,Role}, D, [postpone]}; + handle_event(Type, Ev, StateName, D) -> Descr = case catch atom_to_list(element(1,Ev)) of "ssh_msg_" ++_ when Type==internal -> %% "Message in wrong state"; -lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); + lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); _ -> "Internal error" end, @@ -1689,11 +1695,20 @@ cache(#data{connection_state=C}) -> C#connection.channel_cache. handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> % The peer sent this although we didn't allow it! D; + handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> lists:foldl(fun ext_info/2, D0, Data). -%% ext_info({ExtName,ExtValue}, D0) -> -%% D0; + +ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) -> + %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table: + SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)), + Ssh = Ssh0#ssh{userauth_pubkeys = + [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","), + lists:member(SigAlg, SupportedAlgs) + ]}, + D0#data{ssh_params = Ssh}; + ext_info(_, D0) -> %% Not implemented D0. diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 6e898b4fde..0886d5b34d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -437,9 +437,7 @@ default(client) -> {pref_public_key_algs, def} => #{default => - %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS - [A || A <- ?SUPPORTED_USER_KEYS, - lists:member(A, ssh_transport:supported_algorithms(public_key))], + ssh_transport:supported_algorithms(public_key), chk => fun check_pref_public_key_algs/1, class => @@ -670,20 +668,8 @@ check_pref_public_key_algs(V) -> PKs = ssh_transport:supported_algorithms(public_key), CHK = fun(A, Ack) -> case lists:member(A, PKs) of - true -> - [A|Ack]; - false -> - %% Check with the documented options, that is, - %% the one we can handle - case lists:member(A,?SUPPORTED_USER_KEYS) of - false -> - %% An algorithm ssh never can handle - error_in_check(A, "Not supported public key"); - true -> - %% An algorithm ssh can handle, but not in - %% this very call - Ack - end + true -> [A|Ack]; + false -> error_in_check(A, "Not supported public key") end end, case lists:foldr( -- cgit v1.2.3