%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-2010. 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("ssh.hrl"). -include("PKCS-1.hrl"). -include("DSS.hrl"). -include_lib("kernel/include/file.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, % del_host_key/2, lookup_user_key/3, ssh_dir/2, file_name/3]). -export([private_identity_key/2]). %% , public_identity_key/2, %% identity_keys/2]). -export([encode_public_key/1, decode_public_key_v2/2]). -import(lists, [reverse/1, append/1]). -define(DBG_PATHS, true). -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), read_public_key_v2(File, "ssh-dss"). private_host_dsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_dsa_key", Opts), read_private_key_v2(File, "ssh-dss"). public_host_rsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_rsa_key.pub", Opts), read_public_key_v2(File, "ssh-rsa"). private_host_rsa_key(Type, Opts) -> File = file_name(Type, "ssh_host_rsa_key", Opts), read_private_key_v2(File, "ssh-rsa"). public_host_key(Type, Opts) -> File = file_name(Type, "ssh_host_key", Opts), case read_private_key_v1(File,public) of {error, enoent} -> read_public_key_v1(File++".pub"); Result -> Result end. private_host_key(Type, Opts) -> File = file_name(Type, "ssh_host_key", Opts), read_private_key_v1(File,private). %% 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. %% 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). do_lookup_host_key(Host, Alg, Opts) -> case file:open(file_name(user, "known_hosts", Opts), [read]) of {ok, Fd} -> Res = lookup_host_key_fd(Fd, Host, Alg), file:close(Fd), Res; {error, enoent} -> {error, not_found}; Error -> Error end. 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. %% del_host_key(Host, Opts) -> %% Host1 = replace_localhost(Host), %% case file:open(file_name(user, "known_hosts", Opts),[write,read]) of %% {ok, Fd} -> %% Res = del_key_fd(Fd, Host1), %% file:close(Fd), %% Res; %% Error -> %% Error %% end. identity_key_filename("ssh-dss") -> "id_dsa"; identity_key_filename("ssh-rsa") -> "id_rsa". private_identity_key(Alg, Opts) -> Path = file_name(user, identity_key_filename(Alg), Opts), read_private_key_v2(Path, Alg). read_public_key_v2(File, Type) -> case file:read_file(File) of {ok,Bin} -> List = binary_to_list(Bin), case lists:prefix(Type, List) of true -> List1 = lists:nthtail(length(Type), List), K_S = ssh_bits:b64_decode(List1), decode_public_key_v2(K_S, Type); false -> {error, bad_format} end; Error -> Error end. decode_public_key_v2(K_S, "ssh-rsa") -> case ssh_bits:decode(K_S,[string,mpint,mpint]) of ["ssh-rsa", E, N] -> {ok, #ssh_key { type = rsa, public = {N,E}, comment=""}}; _ -> {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,#ssh_key { type = dsa, public = {P,Q,G,Y} }}; _A -> {error, bad_format} end; decode_public_key_v2(_, _) -> {error, bad_format}. read_public_key_v1(File) -> case file:read_file(File) of {ok,Bin} -> List = binary_to_list(Bin), case io_lib:fread("~d ~d ~d ~s", List) of {ok,[_Sz,E,N,Comment],_} -> {ok,#ssh_key { type = rsa, public ={N,E}, comment = Comment }}; _Error -> {error, bad_format} end; Error -> Error end. %% pem_type("ssh-dss") -> "DSA"; %% pem_type("ssh-rsa") -> "RSA". read_private_key_v2(File, Type) -> case file:read_file(File) of {ok, PemBin} -> case catch (public_key:pem_decode(PemBin)) of [{_, Bin, not_encrypted}] -> decode_private_key_v2(Bin, Type); Error -> %% Note we do not handle password encrypted keys at the moment {error, Error} end; {error, Reason} -> {error, Reason} end. %% case file:read_file(File) of %% {ok,Bin} -> %% case read_pem(binary_to_list(Bin), pem_type(Type)) of %% {ok,Bin1} -> %% decode_private_key_v2(Bin1, Type); %% Error -> %% Error %% end; %% Error -> %% Error %% end. decode_private_key_v2(Private,"ssh-rsa") -> case 'PKCS-1':decode( 'RSAPrivateKey', Private) of {ok,RSA} -> %% FIXME Check for two-prime version {ok, #ssh_key { type = rsa, public = {RSA#'RSAPrivateKey'.modulus, RSA#'RSAPrivateKey'.publicExponent}, private = {RSA#'RSAPrivateKey'.modulus, RSA#'RSAPrivateKey'.privateExponent} }}; Error -> Error end; decode_private_key_v2(Private, "ssh-dss") -> case 'DSS':decode('DSAPrivateKey', Private) of {ok,DSA} -> %% FIXME Check for two-prime version {ok, #ssh_key { type = dsa, public = {DSA#'DSAPrivateKey'.p, DSA#'DSAPrivateKey'.q, DSA#'DSAPrivateKey'.g, DSA#'DSAPrivateKey'.y}, private= {DSA#'DSAPrivateKey'.p, DSA#'DSAPrivateKey'.q, DSA#'DSAPrivateKey'.g, DSA#'DSAPrivateKey'.x} }}; _ -> {error,bad_format} end. %% SSH1 private key format %% <<"SSH PRIVATE KEY FILE FORMATE 1.1\n" 0:8 %% CipherNum:8, Reserved:32, %% NSz/uint32, N/bignum, E/bignum, Comment/string, %% %% [ R0:8 R1:8 R0:8 R1:8, D/bignum, IQMP/bignum, Q/bignum, P/bignum, Pad(8)]>> %% %% where [ ] is encrypted using des3 (ssh1 version) and %% a posssibly empty pass phrase using md5(passphase) as key %% read_private_key_v1(File, Type) -> case file:read_file(File) of {ok,<<"SSH PRIVATE KEY FILE FORMAT 1.1\n",0, CipherNum,_Resereved:32,Bin/binary>>} -> decode_private_key_v1(Bin, CipherNum,Type); {ok,_} -> {error, bad_format}; Error -> Error end. decode_private_key_v1(Bin, CipherNum, Type) -> case ssh_bits:decode(Bin,0,[uint32, bignum, bignum, string]) of {Offset,[_NSz,N,E,Comment]} -> if Type == public -> {ok,#ssh_key { type=rsa, public={N,E}, comment=Comment}}; Type == private -> <<_:Offset/binary, Encrypted/binary>> = Bin, case ssh_bits:decode(decrypt1(Encrypted, CipherNum),0, [uint32, bignum, bignum, bignum, bignum,{pad,8}]) of {_,[_,D,IQMP,Q,P]} -> {ok,#ssh_key { type=rsa, public={N,E}, private={D,IQMP,Q,P}, comment=Comment}}; _ -> {error,bad_format} end end; _ -> {error,bad_format} end. decrypt1(Bin, CipherNum) -> decrypt1(Bin, CipherNum,""). decrypt1(Bin, CipherNum, Phrase) -> if CipherNum == ?SSH_CIPHER_NONE; Phrase == "" -> Bin; CipherNum == ?SSH_CIPHER_3DES -> <> = erlang:md5(Phrase), K3 = K1, IV = <<0,0,0,0,0,0,0,0>>, Bin1 = crypto:des_cbc_decrypt(K3,IV,Bin), Bin2 = crypto:des_cbc_encrypt(K2,IV,Bin1), crypto:des_cbc_decrypt(K1,IV,Bin2) end. %% encrypt1(Bin, CipherNum) -> %% encrypt1(Bin, CipherNum,""). %% encrypt1(Bin, CipherNum, Phrase) -> %% if CipherNum == ?SSH_CIPHER_NONE; Phrase == "" -> %% Bin; %% CipherNum == ?SSH_CIPHER_3DES -> %% <> = erlang:md5(Phrase), %% K3 = K1, %% IV = <<0,0,0,0,0,0,0,0>>, %% Bin1 = crypto:des_cbc_encrypt(K1,IV,Bin), %% Bin2 = crypto:des_cbc_decrypt(K2,IV,Bin1), %% crypto:des_cbc_encrypt(K3,IV,Bin2) %% end. lookup_host_key_fd(Fd, Host, Alg) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case string:tokens(Line, " ") of [HostList, Alg, KeyData] -> %% io:format(" ~p lookup_host_key_fd: HostList ~p Alg ~p KeyData ~p\n", %% [Host, HostList, Alg, KeyData]), case lists:member(Host, string:tokens(HostList, ",")) of true -> decode_public_key_v2(ssh_bits:b64_decode(KeyData), Alg); false -> lookup_host_key_fd(Fd, Host, Alg) end; _ -> lookup_host_key_fd(Fd, Host, Alg) end end. %% del_key_fd(Fd, Host) -> %% del_key_fd(Fd, Host, 0, 0). %% del_key_fd(Fd, Host, ReadPos0, WritePos0) -> %% case io:get_line(Fd, '') of %% eof -> %% if ReadPos0 == WritePos0 -> %% ok; %% true -> %% file:truncate(Fd) %% end; %% Line -> %% {ok,ReadPos1} = file:position(Fd, cur), %% case string:tokens(Line, " ") of %% [HostList, _Type, _KeyData] -> %% case lists:member(Host, string:tokens(HostList, ",")) of %% true -> %% del_key_fd(Fd, Host, ReadPos1, WritePos0); %% false -> %% if ReadPos0 == WritePos0 -> %% del_key_fd(Fd, Host, ReadPos1, ReadPos1); %% true -> %% file:position(Fd, WritePos0), %% file:write(Fd, Line), %% {ok,WritePos1} = file:position(Fd,cur), %% del_key_fd(Fd, Host, ReadPos1, WritePos1) %% end %% end; %% _ -> %% if ReadPos0 == WritePos0 -> %% del_key_fd(Fd, Host, ReadPos1, ReadPos1); %% true -> %% file:position(Fd, WritePos0), %% file:write(Fd, Line), %% {ok,WritePos1} = file:position(Fd,cur), %% del_key_fd(Fd, Host, ReadPos1, WritePos1) %% end %% end %% end. add_key_fd(Fd, Host, Key) -> case Key#ssh_key.type of rsa -> {N,E} = Key#ssh_key.public, DK = ssh_bits:b64_encode( ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint])), file:write(Fd, [Host, " ssh-rsa ", DK, "\n"]); dsa -> {P,Q,G,Y} = Key#ssh_key.public, DK = ssh_bits:b64_encode( ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint])), file:write(Fd, [Host, " ssh-dss ", DK, "\n"]) end. %% read_pem(Cs, Type) -> %% case read_line(Cs) of %% {"-----BEGIN "++Rest,Cs1} -> %% case string:tokens(Rest, " ") of %% [Type, "PRIVATE", "KEY-----"] -> %% read_pem64(Cs1, [], Type); %% _ -> %% {error, bad_format} %% end; %% {"",Cs1} when Cs1 =/= "" -> %% read_pem(Cs1,Type); %% {_,""} -> %% {error, bad_format} %% end. %% read_pem64(Cs, Acc, Type) -> %% case read_line(Cs) of %% {"-----END "++Rest,_Cs1} -> %% case string:tokens(Rest, " ") of %% [Type, "PRIVATE", "KEY-----"] -> %% {ok,ssh_bits:b64_decode(append(reverse(Acc)))}; %% Toks -> %% error_logger:format("ssh: TOKENS=~p\n", [Toks]), %% {error, bad_format} %% end; %% {B64, Cs1} when Cs1 =/= "" -> %% read_pem64(Cs1, [B64|Acc], Type); %% _What -> %% {error, bad_format} %% end. %% read_line(Cs) -> read_line(Cs,[]). %% read_line([$\r,$\n|T], Acc) -> {reverse(Acc), T}; %% read_line([$\n|T], Acc) -> {reverse(Acc), T}; %% read_line([C|T], Acc) -> read_line(T,[C|Acc]); %% read_line([], Acc) -> {reverse(Acc),[]}. lookup_user_key(User, Alg, Opts) -> SshDir = ssh_dir({remoteuser,User}, Opts), case lookup_user_key_f(User, SshDir, Alg, "authorized_keys", Opts) of {ok, Key} -> {ok, Key}; _ -> lookup_user_key_f(User, SshDir, Alg, "authorized_keys2", Opts) end. 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(_User, Dir, Alg, F, _Opts) -> FileName = filename:join(Dir, F), case file:open(FileName, [read]) of {ok, Fd} -> Res = lookup_user_key_fd(Fd, Alg), file:close(Fd), Res; {error, Reason} -> {error, {{openerr, Reason}, {file, FileName}}} end. lookup_user_key_fd(Fd, Alg) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case string:tokens(Line, " ") of [Alg, KeyData, _] -> %% io:format("lookup_user_key_fd: HostList ~p Alg ~p KeyData ~p\n", %% [HostList, Alg, KeyData]), decode_public_key_v2(ssh_bits:b64_decode(KeyData), Alg); _Other -> %%?dbg(false, "key_fd Other: ~w ~w\n", [Alg, _Other]), lookup_user_key_fd(Fd, Alg) end end. encode_public_key(#ssh_key{type = rsa, public = {N, E}}) -> ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint]); encode_public_key(#ssh_key{type = dsa, public = {P,Q,G,Y}}) -> ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]). %% %% 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), %%?dbg(?DBG_PATHS, "file_name: ~p\n", [FN]), FN. 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.