diff options
author | Erlang/OTP <otp@erlang.org> | 2021-05-20 16:05:36 +0200 |
---|---|---|
committer | Erlang/OTP <otp@erlang.org> | 2021-05-20 16:05:36 +0200 |
commit | b41d3d067a1096bb05d3ddcdfc642bce3e5dda28 (patch) | |
tree | 7c9be378adbefe5c5c344c55ebc3e6a1930abfa9 | |
parent | 583cba31eb09c14abd0b217fe7ac2e9a60425d51 (diff) | |
parent | 4ac011946482a62613c8925eaa0307ca8eedb899 (diff) | |
download | erlang-b41d3d067a1096bb05d3ddcdfc642bce3e5dda28.tar.gz |
Merge branch 'hans/ssh/add_missing_to_ssh_file/OTP-17397' into maint-24
* hans/ssh/add_missing_to_ssh_file/OTP-17397:
ssh: Accept \r\n as well as \n as line end
ssh: ssh_file:encode/decode of known_host and authorized_keys files
-rw-r--r-- | lib/ssh/src/ssh_file.erl | 172 | ||||
-rw-r--r-- | lib/ssh/test/ssh_pubkey_SUITE.erl | 3 |
2 files changed, 119 insertions, 56 deletions
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 9381633faa..4815d4238b 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -174,13 +174,21 @@ add_host_key(Hosts0, Port, Key, Opts) -> decode(KeyBin, ssh2_pubkey) when is_binary(KeyBin) -> ssh_message:ssh2_pubkey_decode(KeyBin); +decode(KeyBin, public_key) when is_binary(KeyBin) -> + Type = case KeyBin of + <<"-----BEGIN OPENSSH",_/binary>> -> openssh_key_v1; + <<"----",_/binary>> -> rfc4716_key; + _ -> openssh_key + end, + decode(KeyBin, Type); + decode(KeyBin, Type) when is_binary(KeyBin) andalso (Type==rfc4716_key orelse Type==openssh_key_v1 % Experimental ) -> %% Ex: <<"---- BEGIN SSH2 PUBLIC KEY ----\n....">> (rfc4716_key) %% <<"-----BEGIN OPENSSH PRIVATE KEY-----\n....">> (openssh_key_v1) - case ssh_file:decode_ssh_file(public, any, KeyBin, ignore) of + case decode_ssh_file(public, any, KeyBin, ignore) of {ok,Keys} -> [{Key, if @@ -199,8 +207,6 @@ decode(KeyBin, Type) when is_binary(KeyBin) andalso decode(KeyBin0, openssh_key) when is_binary(KeyBin0) -> %% Ex: <<"ssh-rsa AAAAB12....3BC someone@example.com">> try - Lines = [L || L <- binary:split(KeyBin0, <<"\n">>, [global,trim_all]), - re:run(L, "^\s+$") == nomatch], [begin [_,K|Rest] = binary:split(Line, <<" ">>, [global,trim_all]), Key = ssh_message:ssh2_pubkey_decode(base64:decode(K)), @@ -208,30 +214,66 @@ decode(KeyBin0, openssh_key) when is_binary(KeyBin0) -> [Comment] -> {Key, [{comment,binary_to_list(Comment)}]}; [] -> {Key,[]} end - end || Line <- Lines, - case Line of - <<"#",_/binary>> -> % skip comments - false; - _ -> - true - end + end || Line <- split_in_nonempty_lines(KeyBin0) ] catch _:_ -> {error, key_decode_failed} end; -decode(KeyBin, public_key) when is_binary(KeyBin) -> - Type = case KeyBin of - <<"-----BEGIN OPENSSH",_/binary>> -> openssh_key_v1; - <<"----",_/binary>> -> rfc4716_key; - _ -> openssh_key - end, - decode(KeyBin, Type); - -decode(_KeyBin, Type) when Type == known_hosts; - Type == auth_keys - -> - {error, not_yet_implemented}; +decode(Bin, known_hosts) when is_binary(Bin) -> + [begin + Attrs = + [ + {comment, binary_to_list(erlang:iolist_to_binary(lists:join(" ", Comment)))} + || Comment =/= [] + ] ++ + [ + {hostnames, + [binary_to_list(HP) + || HP <- binary:split(HostPort,<<",">>,[global,trim_all]) + ]} + ], + {ssh_message:ssh2_pubkey_decode(base64:decode(KeyBin)), + Attrs + } + end + || L <- split_in_nonempty_lines(Bin), + [HostPort,_KeyType,KeyBin|Comment] <- [binary:split(L,<<" ">>,[global,trim_all])] + ]; + +decode(Bin, auth_keys) when is_binary(Bin) -> + [begin + Attrs = + [ + {comment, binary_to_list(erlang:iolist_to_binary(lists:join(" ", Comment)))} + || Comment =/= [] + ] ++ + [ + {options, lists:map(fun erlang:binary_to_list/1, Options)} + || Options =/= [] + ], + {ssh_message:ssh2_pubkey_decode(base64:decode(KeyBin)), + Attrs + } + end + || L <- split_in_nonempty_lines(Bin), + [Options,_KeyType,KeyBin|Comment] <- + case binary:match(L, [<<"ssh-rsa">>, + <<"rsa-sha2-">>, + <<"ssh-dss">>, + <<"ecdsa-sha2-nistp">>, + <<"ssh-ed">> + ]) of + nomatch -> + []; + {0, Len} when is_integer(Len) -> + [ [[] | binary:split(L,<<" ">>,[global,trim_all])] ]; + {Pos,Len} when is_integer(Pos), is_integer(Len) -> + [ [binary:split(binary:part(L,0,Pos-1), <<",">>,[global,trim_all]) | + binary:split(binary:part(L,Pos,size(L)-Pos), <<" ">>, [global,trim_all])] + ] + end + ]; decode(_KeyBin, _Type) -> error(badarg). @@ -271,7 +313,7 @@ encode(KeyAttrs, Type) when Type==rfc4716_key ; [ [Begin, [rfc4716_encode_header(H) || H <- proplists:get_value(headers, Attrs, [])], - split_lines( base64:encode( F(Key) ) ), + split_long_lines( base64:encode( F(Key) ) ), "\n", End ] || @@ -279,19 +321,36 @@ encode(KeyAttrs, Type) when Type==rfc4716_key ; ] ); -encode(KeyAttrs, openssh_key) -> +encode(KeyAttrs, Type) when Type == known_hosts; + Type == auth_keys ; + Type == openssh_key -> + FirstArgTag = + case Type of + known_hosts -> hostnames; + auth_keys -> options; + openssh_key -> '*no tag*' + end, iolist_to_binary( - [begin - <<?DEC_BIN(KeyType,__0),_/binary>> = Enc0 = ssh_message:ssh2_pubkey_encode(Key), - [KeyType, " ", base64:encode(Enc0), " ", proplists:get_value(comment,Attrs,""), "\n"] - end || - {Key,Attrs} <- KeyAttrs - ]); + [ + begin + <<?DEC_BIN(KeyType,__0),_/binary>> = Enc = ssh_message:ssh2_pubkey_encode(Key), + [case lists:join(",", proplists:get_value(FirstArgTag, Attributes, [])) of + [] -> ""; + C -> [C," "] + end, + KeyType, " ", + base64:encode(Enc), " ", + case proplists:get_value(comment, Attributes, []) of + [] -> ""; + C -> C + end, + "\n" + ] + end + || {Key,Attributes} <- KeyAttrs + ] + ); -encode(_KeyBin, Type) when Type == known_hosts; - Type == auth_keys - -> - {error, not_yet_implemented}; encode(_KeyBin, _Type) -> error(badarg). @@ -307,7 +366,7 @@ lookup_auth_keys(KeyType, Key, File, Opts) -> time -> case file:read_file(File) of {ok,Bin} -> - Lines = binary:split(Bin, <<"\n">>, [global,trim_all]), + Lines = split_in_lines(Bin), find_key(KeyType, Key, Lines); _ -> false @@ -323,7 +382,7 @@ lookup_auth_keys(KeyType, Key, File, Opts) -> file:close(Fd), Result; {error,_Error} -> - false + false end; Other -> {error,{is_auth_key,{opt,Other}}} @@ -419,7 +478,7 @@ read_test_loop(Fd, Test) -> %% Rare... For example NFS errors {error,Error}; Line0 -> - case binary:split(Line0, <<"\n">>, [global,trim_all]) of % remove trailing \n + case split_in_lines(Line0) of % remove trailing EOL [Line] -> case Test(Line) of false -> @@ -439,7 +498,7 @@ lookup_host_keys(Hosts, KeyType, Key, File, Opts) -> time -> case file:read_file(File) of {ok,Bin} -> - Lines = binary:split(Bin, <<"\n">>, [global,trim_all]), + Lines = split_in_lines(Bin), case find_host_key(Hosts, KeyType, Key, Lines) of {true,RestLines} -> case revoked_key(Hosts, KeyType, Key, RestLines) of @@ -641,11 +700,9 @@ rfc4716_encode_value(Value) -> Value end. -split_lines(<<Text:?ENCODED_LINE_LENGTH/binary>>) -> - [Text]; -split_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) -> - [Text, $\n | split_lines(Rest)]; -split_lines(Bin) -> +split_long_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) when Rest =/= <<"">> -> + [Text, $\n | split_long_lines(Rest)]; +split_long_lines(Bin) -> [Bin]. %%%---------------- COMMON FUNCTIONS ------------------------------ @@ -744,9 +801,9 @@ decode_ssh_file(PrivPub, Algorithm, Pem, Password) -> decode_pem_keys(RawBin, Password) -> - PemLines = binary:split( - binary:replace(RawBin, <<"\\\n">>, <<"">>, [global]), - <<"\n">>, [global,trim_all]), + PemLines = split_in_lines( + binary:replace(RawBin, [<<"\\\n">>,<<"\\\r\\\n">>], <<"">>, [global]) + ), decode_pem_keys(PemLines, Password, []). decode_pem_keys([], _, Acc) -> {ok,lists:reverse(Acc)}; @@ -763,15 +820,6 @@ decode_pem_keys(PemLines, Password, Acc) -> Key <- [Pub,Priv]], decode_pem_keys(RestLines, Password, Keys ++ Acc); - %% {'openssh-key-v1', Bin, Attrs, RestLines} -> - %% %% -----BEGIN OPENSSH PRIVATE KEY----- - %% %% Holds both public and private keys - %% KeyPairsCmnts = openssh_key_v1_decode(Bin, Password), - %% %% Keys = [{Key,{Attrs,Cmnt}} || {Pub,Priv,Cmnt} <- KeyPairsCmnts, - %% %% Key <- [Pub,Priv]], - %% decode_pem_keys(RestLines, Password, [{KeyPairsCmnts,Attrs}| Acc]); - - {rfc4716, Bin, Attrs, RestLines} -> %% ---- BEGIN SSH2 PUBLIC KEY ---- %% rfc4716 only defines public keys @@ -1084,3 +1132,17 @@ pad(N, BlockSize) -> list_to_binary(lists:seq(1,BlockSize-N)). %%%================================================================ %%% +split_in_nonempty_lines(Bin) -> + skip_blank_lines_and_comments( split_in_lines(Bin) ). + +split_in_lines(Bin) -> + binary:split(Bin, [<<"\n">>,<<"\r\n">>], [global,trim_all]). + +skip_blank_lines_and_comments(Lines) -> + lists:filter(fun(<<"#",_/binary>>) -> + %% skip comments + false; + (L) -> + %% skip blank lines + re:run(L, "^(\t|\s)+$") == nomatch + end, Lines). diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl index aa3f6bc9b5..e703318e06 100644 --- a/lib/ssh/test/ssh_pubkey_SUITE.erl +++ b/lib/ssh/test/ssh_pubkey_SUITE.erl @@ -158,7 +158,8 @@ groups() -> ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment, ssh_rfc4716_rsa_subject, ssh_list_public_key, -%% ssh_known_hosts, ssh_auth_keys, ssh1_known_hosts, ssh1_auth_keys, + ssh_known_hosts, %% ssh1_known_hosts, + ssh_auth_keys, %% ssh1_auth_keys, ssh_openssh_key_with_comment, ssh_openssh_key_long_header]} ]. |