%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% -module(ssh_auth). -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). -include("ssh_auth.hrl"). -include("ssh_transport.hrl"). -export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, handle_userauth_info_request/3, handle_userauth_info_response/2, userauth_messages/0 ]). %%-------------------------------------------------------------------- %%% Internal application API %%-------------------------------------------------------------------- publickey_msg([Cb, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> Alg = algorithm(Cb), Hash = sha, %% Maybe option?! ssh_bits:install_messages(userauth_pk_messages()), case ssh_file:private_identity_key(Alg, Opts) of {ok, Key} -> PubKeyBlob = ssh_file:encode_public_key(Key), SigData = build_sig_data(SessionId, User, Service, Key, PubKeyBlob), Sig = sign(SigData, Hash, Key), SigBlob = list_to_binary([?string(algorithm(Key)), ?binary(Sig)]), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "publickey", data = [?TRUE, ?string(algorithm(Key)), ?binary(PubKeyBlob), ?binary(SigBlob)]}, Ssh); _Error -> not_ok end. password_msg([#ssh{opts = Opts, io_cb = IoCb, user = User, service = Service} = Ssh]) -> ssh_bits:install_messages(userauth_passwd_messages()), Password = case proplists:get_value(password, Opts) of undefined -> IoCb:read_password("ssh password: "); PW -> PW end, ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "password", data = <<?BOOLEAN(?FALSE), ?STRING(list_to_binary(Password))>>}, Ssh). %% See RFC 4256 for info on keyboard-interactive keyboard_interactive_msg([#ssh{user = User, service = Service} = Ssh]) -> ssh_bits:install_messages(userauth_keyboard_interactive_messages()), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "keyboard-interactive", data = << ?STRING(<<"">>), ?STRING(<<>>) >> }, Ssh). service_request_msg(Ssh) -> ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"}, Ssh#ssh{service = "ssh-userauth"}). init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> case user_name(Opts) of {ok, User} -> Msg = #ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "none", data = <<>>}, CbFirst = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG), CbSecond = other_cb(CbFirst), AllowUserInt = proplists:get_value(allow_user_interaction, Opts, true), Prefs = method_preference(CbFirst, CbSecond, AllowUserInt), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, userauth_methods = none, service = "ssh-connection"}); {error, no_user} -> ErrStr = "Could not determine the users name", throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, description = ErrStr, language = "en"}) end. userauth_request_msg(#ssh{userauth_preference = []} = Ssh) -> Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, description = "Unable to connect using the available" " authentication methods", language = "en"}, {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)}; userauth_request_msg(#ssh{userauth_methods = Methods, userauth_preference = [{Pref, Module, Function, Args} | Prefs]} = Ssh0) -> Ssh = Ssh0#ssh{userauth_preference = Prefs}, case lists:member(Pref, Methods) of true -> case Module:Function(Args ++ [Ssh]) of not_ok -> userauth_request_msg(Ssh); Result -> Result end; false -> userauth_request_msg(Ssh) end. handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"}, _, Ssh) -> {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name}, Ssh#ssh{service = "ssh-connection"})}; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "password", data = Data}, _, #ssh{opts = Opts} = Ssh) -> <<_:8, ?UINT32(Sz), BinPwd:Sz/binary>> = Data, Password = binary_to_list(BinPwd), case check_password(User, Password, Opts) of true -> {authorized, User, ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; false -> {not_authorized, {User, {passwd, Password}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ authentications = "", partial_success = false}, Ssh)} end; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "none"}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( #ssh_msg_userauth_failure{authentications = Methods, partial_success = false}, Ssh)}; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", data = Data}, SessionId, #ssh{opts = Opts} = Ssh) -> <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary, ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data, Alg = binary_to_list(BAlg), case HaveSig of ?TRUE -> case verify_sig(SessionId, User, "ssh-connection", Alg, KeyBlob, SigWLen, Opts) of true -> {authorized, User, ssh_transport:ssh_packet( #ssh_msg_userauth_success{}, Ssh)}; false -> {not_authorized, {User, {error, "Invalid signature"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ authentications="publickey,password", partial_success = false}, Ssh)} end; ?FALSE -> ssh_bits:install_messages(userauth_pk_messages()), {not_authorized, {User, undefined}, ssh_transport:ssh_packet( #ssh_msg_userauth_pk_ok{algorithm_name = Alg, key_blob = KeyBlob}, Ssh)} end; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = Other}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, {authmethod, Other}}, ssh_transport:ssh_packet( #ssh_msg_userauth_failure{authentications = Methods, partial_success = false}, Ssh)}. handle_userauth_info_request( #ssh_msg_userauth_info_request{name = Name, instruction = Instr, num_prompts = NumPrompts, data = Data}, IoCb, #ssh{opts = Opts} = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), Resps = keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos), %%?dbg(true, "keyboard_interactive_reply: resps=~n#~p ~n", [Resps]), RespBin = list_to_binary( lists:map(fun(S) -> <<?STRING(list_to_binary(S))>> end, Resps)), {ok, ssh_transport:ssh_packet( #ssh_msg_userauth_info_response{num_responses = NumPrompts, data = RespBin}, Ssh)}. handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, description = "Server does not support" "keyboard-interactive", language = "en"}). userauth_messages() -> [ {ssh_msg_userauth_request, ?SSH_MSG_USERAUTH_REQUEST, [string, string, string, '...']}, {ssh_msg_userauth_failure, ?SSH_MSG_USERAUTH_FAILURE, [string, boolean]}, {ssh_msg_userauth_success, ?SSH_MSG_USERAUTH_SUCCESS, []}, {ssh_msg_userauth_banner, ?SSH_MSG_USERAUTH_BANNER, [string, string]}]. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- method_preference(Callback1, Callback2, true) -> [{"publickey", ?MODULE, publickey_msg, [Callback1]}, {"publickey", ?MODULE, publickey_msg,[Callback2]}, {"password", ?MODULE, password_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ]; method_preference(Callback1, Callback2, false) -> [{"publickey", ?MODULE, publickey_msg, [Callback1]}, {"publickey", ?MODULE, publickey_msg,[Callback2]}, {"password", ?MODULE, password_msg, []} ]. user_name(Opts) -> Env = case os:type() of {win32, _} -> "USERNAME"; {unix, _} -> "LOGNAME" end, case proplists:get_value(user, Opts, os:getenv(Env)) of false -> case os:getenv("USER") of false -> {error, no_user}; User -> {ok, User} end; User -> {ok, User} end. check_password(User, Password, Opts) -> %%?dbg(true, " ~p ~p ~p ~n", [User, Password, Opts]), case proplists:get_value(pwdfun, Opts) of undefined -> Static = get_password_option(Opts, User), Password == Static; Cheker -> Cheker(User, Password) end. get_password_option(Opts, User) -> Passwords = proplists:get_value(user_passwords, Opts, []), case lists:keysearch(User, 1, Passwords) of {value, {User, Pw}} -> Pw; false -> proplists:get_value(password, Opts, false) end. verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> {ok, Key} = ssh_file:decode_public_key_v2(KeyBlob, Alg), case ssh_file:lookup_user_key(Key, User, Alg, Opts) of {ok, OurKey} -> case OurKey of Key -> PlainText = build_sig_data(SessionId, User, Service, Key, KeyBlob), <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen, <<?UINT32(AlgLen), _Alg:AlgLen/binary, ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig, verify(PlainText, sha, Sig, Key); _ -> {error, key_unacceptable} end; Error -> Error end. build_sig_data(SessionId, User, Service, Key, KeyBlob) -> Sig = [?binary(SessionId), ?SSH_MSG_USERAUTH_REQUEST, ?string(User), ?string(Service), ?binary(<<"publickey">>), ?TRUE, ?string(algorithm(Key)), ?binary(KeyBlob)], list_to_binary(Sig). algorithm(ssh_rsa) -> "ssh-rsa"; algorithm(ssh_dsa) -> "ssh-dss"; algorithm(#'RSAPrivateKey'{}) -> "ssh-rsa"; algorithm(#'DSAPrivateKey'{}) -> "ssh-dss"; algorithm({_, #'Dss-Parms'{}}) -> "ssh-dss"; algorithm(#'RSAPublicKey'{}) -> "ssh-rsa". sign(SigData, Hash, #'DSAPrivateKey'{} = Key) -> DerSignature = public_key:sign(SigData, Hash, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>; sign(SigData, Hash, Key) -> public_key:sign(SigData, Hash, Key). %% sign(SigData, _, #'DSAPrivateKey'{} = Key) -> %% ssh_dsa:sign(Key, SigData). verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), public_key:verify(PlainText, Hash, Signature, Key); verify(PlainText, Hash, Sig, Key) -> public_key:verify(PlainText, sha, Sig, Key). %% verify(PlainText, _Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> %% ssh_dsa:verify(Key, PlainText, Sig). decode_keyboard_interactive_prompts(NumPrompts, Data) -> Types = lists:append(lists:duplicate(NumPrompts, [string, boolean])), pairwise_tuplify(ssh_bits:decode(Data, Types)). pairwise_tuplify([E1, E2 | Rest]) -> [{E1, E2} | pairwise_tuplify(Rest)]; pairwise_tuplify([]) -> []. keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> NumPrompts = length(PromptInfos), case proplists:get_value(keyboard_interact_fun, Opts) of undefined when NumPrompts == 1 -> %% Special case/fallback for just one prompt %% (assumed to be the password prompt) case proplists:get_value(password, Opts) of undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos); PW -> [PW] end; undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos); KbdInteractFun -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), case KbdInteractFun(Name, Instr, Prompts) of Rs when length(Rs) == NumPrompts -> Rs; Rs -> erlang:error({mismatching_number_of_responses, {got,Rs}, {expected,NumPrompts}}) end end. keyboard_interact(IoCb, Name, Instr, Prompts) -> if Name /= "" -> IoCb:format("~s", [Name]); true -> ok end, if Instr /= "" -> IoCb:format("~s", [Instr]); true -> ok end, lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt); ({Prompt, false}) -> IoCb:read_password(Prompt) end, Prompts). userauth_passwd_messages() -> [ {ssh_msg_userauth_passwd_changereq, ?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, [string, string]} ]. userauth_keyboard_interactive_messages() -> [ {ssh_msg_userauth_info_request, ?SSH_MSG_USERAUTH_INFO_REQUEST, [string, string, string, uint32, '...']}, {ssh_msg_userauth_info_response, ?SSH_MSG_USERAUTH_INFO_RESPONSE, [uint32, '...']} ]. userauth_pk_messages() -> [ {ssh_msg_userauth_pk_ok, ?SSH_MSG_USERAUTH_PK_OK, [string, % algorithm name binary]} % key blob ]. other_cb(ssh_rsa) -> ssh_dsa; other_cb(ssh_dsa) -> ssh_rsa.