File 3911-ssh-ssh_file-encode-decode-of-known_host-and-authori.patch of Package erlang

From e8e85787f5eefc0e1dcd76b20f5c484822a249bf Mon Sep 17 00:00:00 2001
From: Hans Nilsson <hans@erlang.org>
Date: Fri, 30 Apr 2021 19:25:03 +0200
Subject: [PATCH 1/2] ssh: ssh_file:encode/decode of known_host and
 authorized_keys files

---
 lib/ssh/src/ssh_file.erl          | 172 ++++++++++++++++++++----------
 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..da5ded101b 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">>, <<"">>, [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">>, [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]}
     ].
-- 
2.26.2

openSUSE Build Service is sponsored by