diff options
Diffstat (limited to 'lib/ssh/src')
| -rw-r--r-- | lib/ssh/src/ssh.hrl | 36 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_auth.erl | 112 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 65 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_file.erl | 31 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_message.erl | 8 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_options.erl | 24 | ||||
| -rw-r--r-- | lib/ssh/src/ssh_transport.erl | 76 | 
7 files changed, 216 insertions, 136 deletions
| diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 94b9f3a196..923e9309f4 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -129,6 +129,8 @@  -type pubkey_alg()       :: 'ecdsa-sha2-nistp256' |                              'ecdsa-sha2-nistp384' |                              'ecdsa-sha2-nistp521' | +                            'ssh-ed25519'  | +                            'ssh-ed448'  |                              'rsa-sha2-256' |                              'rsa-sha2-512' |                              'ssh-dss' | @@ -173,7 +175,7 @@  -type common_options() :: [ common_option() ].  -type common_option() ::  -        user_dir_common_option() +        ssh_file:user_dir_common_option()        | profile_common_option()        | max_idle_time_common_option()        | key_cb_common_option() @@ -182,6 +184,7 @@        | ssh_msg_debug_fun_common_option()        | rekey_limit_common_option()        | id_string_common_option() +      | pref_public_key_algs_common_option()        | preferred_algorithms_common_option()        | modify_algorithms_common_option()        | auth_methods_common_option() @@ -191,8 +194,6 @@  -define(COMMON_OPTION, common_option()). - --type user_dir_common_option()      :: {user_dir,  false | string()}.  -type profile_common_option()       :: {profile,   atom() }.  -type max_idle_time_common_option() :: {idle_time, timeout()}.  -type rekey_limit_common_option()   :: {rekey_limit, Bytes::limit_bytes() | @@ -211,6 +212,7 @@          {ssh_msg_debug_fun, fun((ssh:connection_ref(),AlwaysDisplay::boolean(),Msg::binary(),LanguageTag::binary()) -> any()) } .  -type id_string_common_option()           :: {id_string,  string() | random | {random,Nmin::pos_integer(),Nmax::pos_integer()} }. +-type pref_public_key_algs_common_option() :: {pref_public_key_algs, [pubkey_alg()] } .  -type preferred_algorithms_common_option():: {preferred_algorithms, algs_list()}.  -type modify_algorithms_common_option()   :: {modify_algorithms,    modify_algs_list()}.  -type auth_methods_common_option()        :: {auth_methods,         string() }. @@ -223,14 +225,13 @@          {transport, {atom(),atom(),atom()} }        | {vsn, {non_neg_integer(),non_neg_integer()} }        | {tstflg, list(term())} -      | {user_dir_fun, fun()} +      | ssh_file:user_dir_fun_common_option()        | {max_random_length_padding, non_neg_integer()} .  -type client_option()         :: -        pref_public_key_algs_client_option() -      | pubkey_passphrase_client_options() +        ssh_file:pubkey_passphrase_client_options()        | host_accepting_client_options()        | authentication_client_options()        | diffie_hellman_group_exchange_client_option() @@ -241,15 +242,14 @@        | ?COMMON_OPTION .  -type opaque_client_options() :: -        {keyboard_interact_fun, fun((term(),term(),term()) -> term())} +        {keyboard_interact_fun, fun((Name::iodata(), +                                     Instruction::iodata(), +                                     Prompts::[{Prompt::iodata(),Echo::boolean()}] +                                    ) -> +                                      [Response::iodata()] +                                   )}           | opaque_common_options(). --type pref_public_key_algs_client_option() :: {pref_public_key_algs, [pubkey_alg()] } . - --type pubkey_passphrase_client_options() ::   {dsa_pass_phrase,      string()} -                                            | {rsa_pass_phrase,      string()} -                                            | {ecdsa_pass_phrase,    string()} . -  -type host_accepting_client_options() ::          {silently_accept_hosts, accept_hosts()}        | {user_interaction,     boolean()} @@ -299,8 +299,9 @@  -type 'shell_fun/1'() :: fun((User::string()) -> pid()) .  -type 'shell_fun/2'() :: fun((User::string(),  PeerAddr::inet:ip_address()) -> pid()). --type exec_daemon_option()      :: {exec, 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'() }. - +-type exec_daemon_option()      :: {exec, exec_spec()} . +-type exec_spec()               :: {direct, exec_fun()} . +-type exec_fun()                :: 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'().  -type 'exec_fun/1'() :: fun((Cmd::string()) -> exec_result()) .  -type 'exec_fun/2'() :: fun((Cmd::string(), User::string()) -> exec_result()) .  -type 'exec_fun/3'() :: fun((Cmd::string(), User::string(), ClientAddr::ip_port()) -> exec_result()) . @@ -311,7 +312,7 @@  -type send_ext_info_daemon_option() :: {send_ext_info, boolean()} .  -type authentication_daemon_options() :: -        {system_dir, string()} +        ssh_file:system_dir_daemon_option()        | {auth_method_kb_interactive_data, prompt_texts() }        | {user_passwords, [{UserName::string(),Pwd::string()}]}        | {password, string()} @@ -386,9 +387,6 @@  	  algorithms,   %% #alg{} -	  key_cb,       %% Private/Public key callback module -	  io_cb,        %% Interaction callback module -  	  send_mac = none, %% send MAC algorithm  	  send_mac_key,  %% key used in send MAC algorithm  	  send_mac_size = 0, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 4e4aa440de..9632168e65 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -91,8 +91,10 @@ unique(L) ->  %%%---- userauth_request_msg "callbacks" -password_msg([#ssh{opts = Opts, io_cb = IoCb, -		   user = User, service = Service} = Ssh0]) -> +password_msg([#ssh{opts = Opts, +		   user = User, +                   service = Service} = Ssh0]) -> +    IoCb = ?GET_INTERNAL_OPT(io_cb, Opts),      {Password,Ssh} =   	case ?GET_OPT(password, Opts) of  	    undefined when IoCb == ssh_no_io -> @@ -137,9 +139,7 @@ keyboard_interactive_msg([#ssh{user = User,  get_public_key(SigAlg, #ssh{opts = Opts}) ->      KeyAlg = key_alg(SigAlg), -    {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), -    UserOpts = ?GET_OPT(user_options, Opts), -    case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of +    case ssh_transport:call_KeyCb(user_key, [KeyAlg], Opts) of          {ok, PrivKey} ->              try                  %% Check the key - the KeyCb may be a buggy plugin @@ -387,11 +387,9 @@ handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name,  							    instruction = Instr,  							    num_prompts = NumPrompts,  							    data  = Data}, -			     #ssh{opts = Opts, -				  io_cb = IoCb -				 } = Ssh) -> +			     #ssh{opts=Opts} = Ssh) ->      PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), -    case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of +    case keyboard_interact_get_responses(Opts, Name, Instr, PromptInfos) of  	not_ok ->  	    not_ok;  	Responses -> @@ -498,9 +496,7 @@ get_password_option(Opts, User) ->  pre_verify_sig(User, KeyBlob, Opts) ->      try  	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]) +        ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts)      catch  	_:_ ->  	    false @@ -509,10 +505,8 @@ pre_verify_sig(User, KeyBlob, Opts) ->  verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) ->      try          Alg = binary_to_list(AlgBin), -        {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), -        UserOpts = ?GET_OPT(user_options, Opts),          Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception -        true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]), +        true = ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts),          PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg),          <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,          <<?UINT32(AlgLen), _Alg:AlgLen/binary, @@ -536,56 +530,78 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> +key_alg('rsa-sha2-256') -> 'ssh-rsa'; +key_alg('rsa-sha2-512') -> 'ssh-rsa'; +key_alg(Alg) -> Alg. + +%%%================================================================ +%%% +%%% Keyboard-interactive +%%%  +  decode_keyboard_interactive_prompts(_NumPrompts, Data) ->      ssh_message:decode_keyboard_interactive_prompts(Data, []). -keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> -    NumPrompts = length(PromptInfos), +keyboard_interact_get_responses(Opts, Name, Instr, PromptInfos) ->      keyboard_interact_get_responses(?GET_OPT(user_interaction, Opts),  				    ?GET_OPT(keyboard_interact_fun, Opts), -				    ?GET_OPT(password, Opts), IoCb, Name, -				    Instr, PromptInfos, Opts, NumPrompts). +				    ?GET_OPT(password, Opts), +                                    Name, +				    Instr, +                                    PromptInfos, +                                    Opts). -keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) -> +%% Don't re-try an already rejected password. This could happen if both keyboard-interactive +%% and password methods are tried: +keyboard_interact_get_responses(_, _, not_ok, _, _, _, _) ->      not_ok; -keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, -				1) when Password =/= undefined -> -    [Password]; %% Password auth implemented with keyboard-interaction and passwd is known -keyboard_interact_get_responses(_, _, _, _, _, _, _, _, 0)  -> + +%% Only one password requestedm and we have got one via the 'password' option for the daemon: +keyboard_interact_get_responses(_, undefined, Pwd, _, _, [_], _) when Pwd =/= undefined -> +    [Pwd]; %% Password auth implemented with keyboard-interaction and passwd is known + +%% No password requested (keyboard-interactive): +keyboard_interact_get_responses(_, _, _, _, _, [], _)  ->      []; -keyboard_interact_get_responses(false, undefined, undefined, _, _, _, [Prompt|_], Opts, _) -> -    ssh_no_io:read_line(Prompt, Opts); %% Throws error as keyboard interaction is not allowed -keyboard_interact_get_responses(true, undefined, _,IoCb, Name, Instr, PromptInfos, Opts, _) -> -    keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); -keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos, _Opts, NumPrompts) -> -    keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). - -keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> + +%% user_interaction is forbidden (by option user_interaction) and we have to ask +%% the user for one or more. +%% Throw an error: +keyboard_interact_get_responses(false, undefined, undefined, _, _, [Prompt|_], Opts) -> +    ssh_no_io:read_line(Prompt, Opts); + +%% One or more passwords are requested, we may prompt the user and no fun is used +%% to get the responses: +keyboard_interact_get_responses(true, undefined, _, Name, Instr, PromptInfos, Opts) -> +    prompt_user_for_passwords(Name, Instr, PromptInfos, Opts); + +%% The passwords are provided with a fun. Use that one! +keyboard_interact_get_responses(true, Fun, _Pwd, Name, Instr, PromptInfos, _Opts) -> +    keyboard_interact_fun(Fun, Name, Instr, PromptInfos). + + + +prompt_user_for_passwords(Name, Instr, PromptInfos, Opts) -> +    IoCb = ?GET_INTERNAL_OPT(io_cb, Opts),      write_if_nonempty(IoCb, Name),      write_if_nonempty(IoCb, Instr),      lists:map(fun({Prompt, true})  -> IoCb:read_line(Prompt, Opts);  		 ({Prompt, false}) -> IoCb:read_password(Prompt, Opts)  	      end, -	      Prompts). +	      PromptInfos). -write_if_nonempty(_, "") -> ok; -write_if_nonempty(_, <<>>) -> ok; -write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). - - -keyboard_interact_fun(KbdInteractFun, Name, Instr,  PromptInfos, NumPrompts) -> -    Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, -			PromptInfos), -    case KbdInteractFun(Name, Instr, Prompts) of -	Rs when length(Rs) == NumPrompts -> -	    Rs; -	_Rs -> +keyboard_interact_fun(KbdInteractFun, Name, Instr,  PromptInfos) -> +    case KbdInteractFun(Name, Instr, PromptInfos) of +	Responses when is_list(Responses), +                     length(Responses) == length(PromptInfos) -> +	    Responses; +	_ ->              nok      end. -key_alg('rsa-sha2-256') -> 'ssh-rsa'; -key_alg('rsa-sha2-512') -> 'ssh-rsa'; -key_alg(Alg) -> Alg. +write_if_nonempty(_, "") -> ok; +write_if_nonempty(_, <<>>) -> ok; +write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 4b41c10cbb..7c87591cf2 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -447,7 +447,6 @@ init_ssh_record(Role, Socket, Opts) ->  init_ssh_record(Role, Socket, PeerAddr, Opts) ->      AuthMethods = ?GET_OPT(auth_methods, Opts),      S0 = #ssh{role = Role, -	      key_cb = ?GET_OPT(key_cb, Opts),  	      opts = Opts,  	      userauth_supported_methods = AuthMethods,  	      available_host_keys = available_hkey_algorithms(Role, Opts), @@ -472,10 +471,11 @@ init_ssh_record(Role, Socket, PeerAddr, Opts) ->              S1 =                  S0#ssh{c_vsn = Vsn,                         c_version = Version, -                       io_cb = case ?GET_OPT(user_interaction, Opts) of -                                   true ->  ssh_io; -                                   false -> ssh_no_io -                               end, +                       opts = ?PUT_INTERNAL_OPT({io_cb, case ?GET_OPT(user_interaction, Opts) of +                                                            true ->  ssh_io; +                                                            false -> ssh_no_io +                                                        end}, +                                                Opts),                         userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts),                         peer = {PeerName, PeerAddr},                         local = LocalName @@ -488,7 +488,6 @@ init_ssh_record(Role, Socket, PeerAddr, Opts) ->  	server ->  	    S0#ssh{s_vsn = Vsn,  		   s_version = Version, -		   io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io),  		   userauth_methods = string:tokens(AuthMethods, ","),  		   kb_tries_left = 3,  		   peer = {undefined, PeerAddr}, @@ -983,6 +982,10 @@ handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive  %%% ######## {connected, client|server} #### +%% Skip ext_info messages in connected state (for example from OpenSSH >= 7.7) +handle_event(_, #ssh_msg_ext_info{}, {connected,_Role}, D) -> +    {keep_state, D}; +  handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) ->      {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params),      D = D0#data{ssh_params = Ssh, @@ -1682,18 +1685,19 @@ peer_role(client) -> server;  peer_role(server) -> client.  %%-------------------------------------------------------------------- -available_hkey_algorithms(Role, Options) -> -    KeyCb = ?GET_OPT(key_cb, Options), -    case [A || A <- available_hkey_algos(Options), -               (Role==client) orelse available_host_key(KeyCb, A, Options) -         ] of -         -        [] when Role==client -> -	    error({shutdown, "No public key algs"}); - -        [] when Role==server -> -	    error({shutdown, "No host key available"}); +available_hkey_algorithms(client, Options) -> +    case available_hkey_algos(Options) of +        [] -> +            error({shutdown, "No public key algs"}); +        Algs -> +	    [atom_to_list(A) || A<-Algs] +    end; +available_hkey_algorithms(server, Options) -> +    case [A || A <- available_hkey_algos(Options), +               is_usable_host_key(A, Options)] of +        [] -> +            error({shutdown, "No host key available"});  	Algs ->  	    [atom_to_list(A) || A<-Algs]      end. @@ -1709,18 +1713,6 @@ available_hkey_algos(Options) ->      AvailableAndSupported. -%% Alg :: atom() -available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) -> -    UserOpts = ?GET_OPT(user_options, Opts), -    case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of -        {ok,Key} -> -            %% Check the key - the KeyCb may be a buggy plugin -            ssh_transport:valid_key_sha_alg(Key, Alg); -        _ -> -            false -    end. - -  send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->      {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0),      send_bytes(Bytes, State), @@ -1840,10 +1832,21 @@ ext_info(_, D0) ->      D0.  %%%---------------------------------------------------------------- -is_usable_user_pubkey(A, Ssh) -> -    case ssh_auth:get_public_key(A, Ssh) of +is_usable_user_pubkey(Alg, Ssh) -> +    try ssh_auth:get_public_key(Alg, Ssh) of          {ok,_} -> true;          _ -> false +    catch +        _:_ -> false +    end. + +%%%---------------------------------------------------------------- +is_usable_host_key(Alg, Opts) -> +    try ssh_transport:get_host_key(Alg, Opts) +    of +        _PrivHostKey -> true +    catch +        _:_ -> false      end.  %%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 832952ed52..510269bbb1 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -39,6 +39,24 @@  	 is_auth_key/3]). +-export_type([system_dir_daemon_option/0, +              user_dir_common_option/0, +              user_dir_fun_common_option/0, +              pubkey_passphrase_client_options/0 +             ]). + +-type system_dir_daemon_option()   :: {system_dir, string()}. +-type user_dir_common_option()     :: {user_dir,  string()}. +-type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}. +-type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) . + +-type pubkey_passphrase_client_options() ::   {dsa_pass_phrase,      string()} +                                            | {rsa_pass_phrase,      string()} +%% Not yet implemented:                     | {ed25519_pass_phrase,  string()} +%% Not yet implemented:                     | {ed448_pass_phrase,    string()} +                                            | {ecdsa_pass_phrase,    string()} . + +  -define(PERM_700, 8#700).  -define(PERM_644, 8#644). @@ -103,6 +121,8 @@ file_base_name('ssh-dss'            ) -> "ssh_host_dsa_key";  file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";  file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";  file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name('ssh-ed25519'        ) -> "ssh_host_ed25519_key"; +file_base_name('ssh-ed448'          ) -> "ssh_host_ed448_key";  file_base_name(_                    ) -> "ssh_host_key".  decode(File, Password) -> @@ -240,6 +260,8 @@ identity_key_filename('ssh-rsa'            ) -> "id_rsa";  identity_key_filename('rsa-sha2-256'       ) -> "id_rsa";  identity_key_filename('rsa-sha2-384'       ) -> "id_rsa";  identity_key_filename('rsa-sha2-512'       ) -> "id_rsa"; +identity_key_filename('ssh-ed25519'        ) -> "id_ed25519"; +identity_key_filename('ssh-ed448'          ) -> "id_ed448";  identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa";  identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa";  identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". @@ -249,9 +271,12 @@ identity_pass_phrase("ssh-rsa"       ) -> rsa_pass_phrase;  identity_pass_phrase("rsa-sha2-256"  ) -> rsa_pass_phrase;  identity_pass_phrase("rsa-sha2-384"  ) -> rsa_pass_phrase;  identity_pass_phrase("rsa-sha2-512"  ) -> rsa_pass_phrase; +%% Not yet implemented: identity_pass_phrase("ssh-ed25519"   ) -> ed25519_pass_phrase; +%% Not yet implemented: identity_pass_phrase("ssh-ed448"     ) -> ed448_pass_phrase;  identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase;  identity_pass_phrase(P) when is_atom(P) ->  -    identity_pass_phrase(atom_to_list(P)). +    identity_pass_phrase(atom_to_list(P)); +identity_pass_phrase(_) -> undefined.  lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) ->      case io:get_line(Fd, '') of @@ -301,6 +326,10 @@ key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) ->  	_ ->  	    false      end; +key_match({ed_pub,ed25519,_}, 'ssh-ed25519') -> +    true; +key_match({ed_pub,ed448,_}, 'ssh-ed448') -> +    true;  key_match(_, _) ->      false. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index da4027a763..d95e58c1bb 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -611,7 +611,13 @@ encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) ->      <<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;  encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) ->      CurveName = public_key:oid2ssh_curvename(OID), -    <<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>. +    <<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>; +encode_signature({ed_pub, ed25519,_}, _SigAlg, Signature) -> +    <<?Ebinary(<<"ssh-ed25519">>), ?Ebinary(Signature)>>; +encode_signature({ed_pub, ed448,_}, _SigAlg, Signature) -> +    <<?Ebinary(<<"ssh-ed448">>), ?Ebinary(Signature)>>. +     +  %%%################################################################  %%%# diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index bc9f2156bc..1010c9be55 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -434,6 +434,18 @@ default(client) ->              class => user_options             }, +%%% Not yet implemented      {ed25519_pass_phrase, def} => +%%% Not yet implemented          #{default => undefined, +%%% Not yet implemented            chk => fun check_string/1, +%%% Not yet implemented            class => user_options +%%% Not yet implemented           }, +%%% Not yet implemented +%%% Not yet implemented      {ed448_pass_phrase, def} => +%%% Not yet implemented          #{default => undefined, +%%% Not yet implemented            chk => fun check_string/1, +%%% Not yet implemented            class => user_options +%%% Not yet implemented           }, +%%% Not yet implemented        {silently_accept_hosts, def} =>            #{default => false,              chk => fun check_silently_accept_hosts/1, @@ -452,12 +464,6 @@ default(client) ->              class => user_options             }, -      {pref_public_key_algs, def} => -          #{default => ssh_transport:default_algorithms(public_key), -            chk => fun check_pref_public_key_algs/1, -            class => user_options -           }, -        {dh_gex_limits, def} =>            #{default => {1024, 6144, 8192},      % FIXME: Is this true nowadays?              chk => fun({Min,I,Max}) -> @@ -523,6 +529,12 @@ default(common) ->               class => user_options              }, +      {pref_public_key_algs, def} => +          #{default => ssh_transport:default_algorithms(public_key), +            chk => fun check_pref_public_key_algs/1, +            class => user_options +           }, +         {preferred_algorithms, def} =>             #{default => ssh:default_algorithms(),               chk => fun check_preferred_algorithms/1, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index c5b0704925..9ff20454cd 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -51,7 +51,9 @@  	 extract_public_key/1,  	 ssh_packet/2, pack/2,           valid_key_sha_alg/2, -	 sha/1, sign/3, verify/5]). +	 sha/1, sign/3, verify/5, +         get_host_key/2, +         call_KeyCb/3]).  -export([dbg_trace/3]). @@ -147,6 +149,8 @@ supported_algorithms(public_key) ->         {'ecdsa-sha2-nistp384',  [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]},         {'ecdsa-sha2-nistp521',  [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]},         {'ecdsa-sha2-nistp256',  [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]}, +       {'ssh-ed25519',          [{public_keys,eddsa}, {curves,ed25519}                    ]}, +       {'ssh-ed448',            [{public_keys,eddsa}, {curves,ed448}                      ]},         {'ssh-rsa',              [{public_keys,rsa},   {hashs,sha}                         ]},         {'rsa-sha2-256',         [{public_keys,rsa},   {hashs,sha256}                      ]},         {'rsa-sha2-512',         [{public_keys,rsa},   {hashs,sha512}                      ]}, @@ -431,7 +435,8 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ;  %%%   handle_kexdh_init(#ssh_msg_kexdh_init{e = E},   		  Ssh0 = #ssh{algorithms = #alg{kex=Kex, -                                                hkey=SignAlg} = Algs}) -> +                                                hkey=SignAlg} = Algs, +                              opts = Opts}) ->      %% server      {G, P} = dh_group(Kex),      if @@ -439,7 +444,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},              Sz = dh_bits(Algs),  	    {Public, Private} = generate_key(dh, [P,G,2*Sz]),  	    K = compute_key(dh, E, Private, [P,G]), -	    MyPrivHostKey = get_host_key(Ssh0, SignAlg), +	    MyPrivHostKey = get_host_key(SignAlg, Opts),  	    MyPubHostKey = extract_public_key(MyPrivHostKey),              H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {E,Public,K}),              H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -578,14 +583,15 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E},  		       #ssh{keyex_key = {{Private, Public}, {G, P}},  			    keyex_info = {Min, Max, NBits},                              algorithms = #alg{kex=Kex, -                                              hkey=SignAlg}} = Ssh0) -> +                                              hkey=SignAlg}, +                            opts = Opts} = Ssh0) ->      %% server      if  	1=<E, E=<(P-1) ->  	    K = compute_key(dh, E, Private, [P,G]),  	    if  		1<K, K<(P-1) -> -		    MyPrivHostKey = get_host_key(Ssh0, SignAlg), +		    MyPrivHostKey = get_host_key(SignAlg, Opts),  		    MyPubHostKey = extract_public_key(MyPrivHostKey),                      H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}),                      H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -653,7 +659,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK  %%%   handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},  		     Ssh0 = #ssh{algorithms = #alg{kex=Kex, -                                                   hkey=SignAlg}}) -> +                                                   hkey=SignAlg}, +                                 opts = Opts}) ->      %% at server      Curve = ecdh_curve(Kex),      {MyPublic, MyPrivate} = generate_key(ecdh, Curve), @@ -661,7 +668,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},  	compute_key(ecdh, PeerPublic, MyPrivate, Curve)      of  	K -> -	    MyPrivHostKey = get_host_key(Ssh0, SignAlg), +	    MyPrivHostKey = get_host_key(SignAlg, Opts),  	    MyPubHostKey = extract_public_key(MyPrivHostKey),              H = kex_hash(Ssh0, MyPubHostKey, sha(Curve), {PeerPublic, MyPublic, K}),              H_SIG = sign(H, sha(SignAlg), MyPrivHostKey), @@ -759,8 +766,7 @@ ext_info_message(#ssh{role=server,                        send_ext_info=true,                        opts = Opts} = Ssh0) ->      AlgsList = lists:map(fun erlang:atom_to_list/1, -                         proplists:get_value(public_key, -                                             ?GET_OPT(preferred_algorithms, Opts))), +                         ?GET_OPT(pref_public_key_algs, Opts)),      Msg = #ssh_msg_ext_info{nr_extensions = 1,                              data = [{"server-sig-algs", string:join(AlgsList,",")}]                             }, @@ -778,10 +784,8 @@ sid(#ssh{session_id = Id},        _) -> Id.  %%  %% The host key should be read from storage  %% -get_host_key(SSH, SignAlg) -> -    #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH, -    UserOpts = ?GET_OPT(user_options, Opts), -    case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of +get_host_key(SignAlg, Opts) -> +    case call_KeyCb(host_key, [SignAlg], Opts) of  	{ok, PrivHostKey} ->              %% Check the key - the KeyCb may be a buggy plugin              case valid_key_sha_alg(PrivHostKey, SignAlg) of @@ -792,6 +796,11 @@ get_host_key(SSH, SignAlg) ->              exit({error, {Result, unsupported_key_type}})      end. +call_KeyCb(F, Args, Opts) -> +    {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts), +    UserOpts = ?GET_OPT(user_options, Opts), +    apply(KeyCb, F, Args ++ [[{key_cb_private,KeyCbOpts}|UserOpts]]). +  extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->      #'RSAPublicKey'{modulus = N, publicExponent = E};  extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> @@ -799,6 +808,8 @@ extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->  extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},  				   publicKey = Q}) ->      {#'ECPoint'{point=Q}, {namedCurve,OID}}; +extract_public_key({ed_pri, Alg, Pub, _Priv}) -> +    {ed_pub, Alg, Pub};  extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) ->      case {Alg, crypto:privkey_to_pubkey(Alg, M)} of          {rsa, [E,N]} -> @@ -858,29 +869,30 @@ accepted_host(Ssh, PeerName, Public, Opts) ->      end. -yes_no(Ssh, Prompt)  -> -    (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts). +yes_no(#ssh{opts=Opts}, Prompt)  -> +    IoCb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io), +    IoCb:yes_no(Prompt, Opts).  fmt_hostkey('ssh-rsa') -> "RSA";  fmt_hostkey('ssh-dss') -> "DSA"; +fmt_hostkey('ssh-ed25519') -> "ED25519"; +fmt_hostkey('ssh-ed448') -> "ED448";  fmt_hostkey(A) when is_atom(A) -> fmt_hostkey(atom_to_list(A));  fmt_hostkey("ecdsa"++_) -> "ECDSA";  fmt_hostkey(X) -> X. -known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh,  +known_host_key(#ssh{opts = Opts, peer = {PeerName,_}} = Ssh,   	       Public, Alg) -> -    UserOpts = ?GET_OPT(user_options, Opts), -    case is_host_key(KeyCb, Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of -	{_,true} -> +    case call_KeyCb(is_host_key, [Public, PeerName, Alg], Opts) of +	true ->  	    ok; -	{_,false} -> +	false ->              DoAdd = ?GET_OPT(save_accepted_host, Opts),  	    case accepted_host(Ssh, PeerName, Public, Opts) of  		true when DoAdd == true -> -		    {_,R} = add_host_key(KeyCb, PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]), -                    R; +		    call_KeyCb(add_host_key, [PeerName, Public], Opts);  		true when DoAdd == false ->                      ok;  		false -> @@ -890,13 +902,6 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}  	    end      end. -is_host_key(KeyCb, Public, PeerName, Alg, Data) -> -    {KeyCb, KeyCb:is_host_key(Public, PeerName, Alg, Data)}. - -add_host_key(KeyCb, PeerName, Public, Data) -> -    {KeyCb, KeyCb:add_host_key(PeerName, Public, Data)}. -     -  %%   Each of the algorithm strings MUST be a comma-separated list of  %%   algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]).  Each  %%   supported (allowed) algorithm MUST be listed in order of preference. @@ -1937,6 +1942,11 @@ valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa'     ) -> true;  valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true;  valid_key_sha_alg(#'DSAPrivateKey'{},  'ssh-dss') -> true; +valid_key_sha_alg({ed_pub, ed25519,_},  'ssh-ed25519') -> true; +valid_key_sha_alg({ed_pri, ed25519,_,_},'ssh-ed25519') -> true; +valid_key_sha_alg({ed_pub, ed448,_},    'ssh-ed448') -> true; +valid_key_sha_alg({ed_pri, ed448,_,_},  'ssh-ed448') -> true; +  valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}},                Alg) -> valid_key_sha_alg_ec(OID, Alg);  valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg);  valid_key_sha_alg(_, _) -> false. @@ -1946,12 +1956,17 @@ valid_key_sha_alg_ec(OID, Alg) ->      Alg == list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). +-dialyzer({no_match, public_algo/1}). +  public_algo(#'RSAPublicKey'{}) ->   'ssh-rsa';  % FIXME: Not right with draft-curdle-rsa-sha2  public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({ed_pub, ed25519,_}) -> 'ssh-ed25519'; +public_algo({ed_pub, ed448,_}) -> 'ssh-ed448';  public_algo({#'ECPoint'{},{namedCurve,OID}}) ->       Curve = public_key:oid2ssh_curvename(OID),      list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). +  sha('ssh-rsa') -> sha;  sha('rsa-sha2-256') -> sha256;  sha('rsa-sha2-384') -> sha384; @@ -1960,6 +1975,8 @@ sha('ssh-dss') -> sha;  sha('ecdsa-sha2-nistp256') -> sha(secp256r1);  sha('ecdsa-sha2-nistp384') -> sha(secp384r1);  sha('ecdsa-sha2-nistp521') -> sha(secp521r1); +sha('ssh-ed25519') -> undefined; % Included in the spec of ed25519 +sha('ssh-ed448') -> undefined; % Included in the spec of ed448  sha(secp256r1) -> sha256;  sha(secp384r1) -> sha384;  sha(secp521r1) -> sha512; @@ -2054,7 +2071,6 @@ ecdh_curve('curve448-sha512'   ) -> x448;  ecdh_curve('curve25519-sha256' ) -> x25519;  ecdh_curve('[email protected]' ) -> x25519. -  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  %%  %% Utils for default_algorithms/1 and supported_algorithms/1 | 
