%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-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% %% %% %%% Description: SSH file handling -module(ssh_file). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). -include("ssh.hrl"). -export([public_host_dsa_key/2,private_host_dsa_key/2, public_host_rsa_key/2,private_host_rsa_key/2, public_host_key/2,private_host_key/2, lookup_host_key/3, add_host_key/3, lookup_user_key/4, ssh_dir/2, file_name/3]). -export([private_identity_key/2, public_identity_key/2]). -export([encode_public_key/1, decode_public_key_v2/2]). -define(PERM_700, 8#700). -define(PERM_644, 8#644). %% API public_host_dsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_dsa_key.pub", Opts), decode(File, public_key). private_host_dsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_dsa_key", Opts), Password = proplists:get_value(password, Opts, ignore), decode(File, Password). public_host_rsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_rsa_key.pub", Opts), decode(File, public_key). private_host_rsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_rsa_key", Opts), Password = proplists:get_value(password, Opts, ignore), decode(File, Password). public_host_key(Type, Opts) -> File = file_name(Type, "ssh_host_key", Opts), decode(File, public_key). private_host_key(Type, Opts) -> File = file_name(Type, "ssh_host_key", Opts), Password = proplists:get_value(password, Opts, ignore), decode(File, Password). private_identity_key(Alg, Opts) -> File = file_name(user, identity_key_filename(Alg), Opts), Password = proplists:get_value(password, Opts, ignore), decode(File, Password). public_identity_key(Alg, Opts) -> File = file_name(user, identity_key_filename(Alg) ++ ".pub", Opts), decode(File, public_key). encode_public_key(#'RSAPrivateKey'{publicExponent = E, modulus = N}) -> ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint]); encode_public_key(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]). decode(File, Password) -> try {ok, decode_ssh_file(read_ssh_file(File), Password)} catch throw:Reason -> {error, Reason}; error:Reason -> {error, Reason} end. read_ssh_file(File) -> {ok, Bin} = file:read_file(File), Bin. %% Public key decode_ssh_file(SshBin, public_key) -> public_key:ssh_decode(SshBin, public_key); %% Private Key decode_ssh_file(Pem, Password) -> case public_key:pem_decode(Pem) of [{_, _, not_encrypted} = Entry] -> public_key:pem_entry_decode(Entry); [Entry] when Password =/= ignore -> public_key:pem_entry_decode(Entry, Password); _ -> throw("No pass phrase provided for private key file") end. %% lookup_host_key %% return {ok, Key(s)} or {error, not_found} %% lookup_host_key(Host, Alg, Opts) -> Host1 = replace_localhost(Host), do_lookup_host_key(Host1, Alg, Opts). add_host_key(Host, Key, Opts) -> Host1 = add_ip(replace_localhost(Host)), KnownHosts = file_name(user, "known_hosts", Opts), case file:open(KnownHosts, [write,append]) of {ok, Fd} -> ok = file:change_mode(KnownHosts, ?PERM_644), Res = add_key_fd(Fd, Host1, Key), file:close(Fd), Res; Error -> Error end. lookup_user_key(Key, User, Alg, Opts) -> SshDir = ssh_dir({remoteuser,User}, Opts), case lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys", Opts) of {ok, Key} -> {ok, Key}; _ -> lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys2", Opts) end. %% %% Utils %% %% server use this to find individual keys for %% an individual user when user tries to login %% with publickey ssh_dir({remoteuser, User}, Opts) -> case proplists:get_value(user_dir_fun, Opts) of undefined -> case proplists:get_value(user_dir, Opts) of undefined -> default_user_dir(); Dir -> Dir end; FUN -> FUN(User) end; %% client use this to find client ssh keys ssh_dir(user, Opts) -> case proplists:get_value(user_dir, Opts, false) of false -> default_user_dir(); D -> D end; %% server use this to find server host keys ssh_dir(system, Opts) -> proplists:get_value(system_dir, Opts, "/etc/ssh"). file_name(Type, Name, Opts) -> FN = filename:join(ssh_dir(Type, Opts), Name), FN. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% in: "host" out: "host,1.2.3.4. add_ip(Host) -> case inet:getaddr(Host, inet) of {ok, Addr} -> case ssh_connection:encode_ip(Addr) of false -> Host; IPString -> Host ++ "," ++ IPString end; _ -> Host end. replace_localhost("localhost") -> {ok, Hostname} = inet:gethostname(), Hostname; replace_localhost(Host) -> Host. do_lookup_host_key(Host, Alg, Opts) -> case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of {ok, Fd} -> Res = lookup_host_key_fd(Fd, Host, Alg), file:close(Fd), {ok, Res}; {error, enoent} -> {error, not_found}; Error -> Error end. identity_key_filename("ssh-dss") -> "id_dsa"; identity_key_filename("ssh-rsa") -> "id_rsa". decode_public_key_v2(K_S, "ssh-rsa") -> case ssh_bits:decode(K_S,[string,mpint,mpint]) of ["ssh-rsa", E, N] -> {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}}; _ -> {error, bad_format} end; decode_public_key_v2(K_S, "ssh-dss") -> case ssh_bits:decode(K_S,[string,mpint,mpint,mpint,mpint]) of ["ssh-dss",P,Q,G,Y] -> {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; _A -> {error, bad_format} end; decode_public_key_v2(_, _) -> {error, bad_format}. lookup_host_key_fd(Fd, Host, KeyType) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case public_key:ssh_decode(Line, known_hosts) of [{Key, Attributes}] -> handle_host(Fd, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); [] -> lookup_host_key_fd(Fd, Host, KeyType) end end. handle_host(Fd, Host, HostList, Key, KeyType) -> Host1 = host_name(Host), case lists:member(Host1, HostList) and key_match(Key, KeyType) of true -> Key; false -> lookup_host_key_fd(Fd, Host, KeyType) end. host_name(Atom) when is_atom(Atom) -> atom_to_list(Atom); host_name(List) -> List. key_match(#'RSAPublicKey'{}, "ssh-rsa") -> true; key_match({_, #'Dss-Parms'{}}, "ssh-dss") -> true; key_match(_, _) -> false. add_key_fd(Fd, Host,Key) -> SshBin = public_key:ssh_encode([{Key, [{hostnames, [Host]}]}], known_hosts), file:write(Fd, SshBin). lookup_user_key_f(_, _User, [], _Alg, _F, _Opts) -> {error, nouserdir}; lookup_user_key_f(_, _User, nouserdir, _Alg, _F, _Opts) -> {error, nouserdir}; lookup_user_key_f(Key, _User, Dir, _Alg, F, _Opts) -> FileName = filename:join(Dir, F), case file:open(FileName, [read, binary]) of {ok, Fd} -> Res = lookup_user_key_fd(Fd, Key), file:close(Fd), Res; {error, Reason} -> {error, {{openerr, Reason}, {file, FileName}}} end. lookup_user_key_fd(Fd, Key) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case public_key:ssh_decode(Line, auth_keys) of [{AuthKey, _}] -> case is_auth_key(Key, AuthKey) of true -> {ok, Key}; false -> lookup_user_key_fd(Fd, Key) end; [] -> lookup_user_key_fd(Fd, Key) end end. is_auth_key(Key, Key) -> true; is_auth_key(_,_) -> false. default_user_dir()-> {ok,[[Home|_]]} = init:get_argument(home), UserDir = filename:join(Home, ".ssh"), ok = filelib:ensure_dir(filename:join(UserDir, "dummy")), ok = file:change_mode(UserDir, ?PERM_700), UserDir.