File 2503-ssh-Add-ECPrivateKey-version-compatibility-and-tests.patch of Package erlang

From 8e3c7726ee5124574ebb3b237a0137209d709fb4 Mon Sep 17 00:00:00 2001
From: Jakub Witczak <kuba@erlang.org>
Date: Wed, 14 Jan 2026 12:08:06 +0100
Subject: [PATCH 3/3] ssh: Add ECPrivateKey version compatibility and tests

- Add backward compatibility for ECPrivateKey version field
  * Support both integer 1 (OTP 26/27) and atom ecPrivkeyVer1 (OTP 28)
  * Fixes pattern match failures after public_key ASN.1 modernization

- Split experimental_openssh_key_v1 typespec into encode/decode variants
  * experimental_openssh_key_v1_encode() - for encoding input
  * experimental_openssh_key_v1_decode() - for decoding output
  * Fixes type asymmetry between encode and decode flows

- Add comprehensive compatibility test
  * Tests EdDSA curves: ed25519, ed448
  * Tests ECDSA curves: secp256r1, secp384r1
  * Verifies both version formats produce identical encoding

- Code cleanup
  * Remove outdated comments
  * Fix whitespace and formatting
  * Update KeyPairs comment to document both tuple formats

Related to commit b50e1482ec (public_key: Add missing macros and definitions)
Fixes #10525, #10526
---
 lib/ssh/src/ssh_file.erl          | 33 +++++++++++++------------------
 lib/ssh/src/ssh_message.erl       | 14 ++++++-------
 lib/ssh/test/ssh_pubkey_SUITE.erl | 33 +++++++++++++++++++++++++++++--
 3 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 42946230e7..4b3de4d94b 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -653,16 +657,12 @@ encode(KeyAttrs, Type) when Type==rfc4716_key ;
                  fun openssh_key_v1_encode/1}
         end,
     iolist_to_binary(
-      [
-       [Begin,
+      [[Begin,
         [rfc4716_encode_header(H) || H <- proplists:get_value(headers, Attrs, [])],
-        split_long_lines( base64:encode( F(Key) ) ),
+        split_long_lines(base64:encode(F(Key))),
         "\n",
-        End
-       ] ||
-          {Key,Attrs} <- KeyAttrs
-      ]
-     );
+        End] ||
+          {Key,Attrs} <- KeyAttrs]);
 
 encode(KeyAttrs, Type) when Type == known_hosts;
                             Type == auth_keys ;
@@ -1179,16 +1179,14 @@ decode_ssh_file(PrivPub, Algorithm, Pem, Password) ->
             {error, key_decode_failed}
     end.
 
-
 decode_pem_keys(RawBin, Password) ->
     PemLines = split_in_lines(
                  binary:replace(RawBin, [<<"\\\n">>,<<"\\\r\\\n">>],  <<"">>, [global])
                 ),
     decode_pem_keys(PemLines, Password, []).
+
 decode_pem_keys([], _, Acc) ->
     {ok,lists:reverse(Acc)};
-
-
 decode_pem_keys(PemLines, Password, Acc) ->
     %% Private Key
     try get_key_part(PemLines) of
@@ -1386,12 +1384,10 @@ openssh_key_v1_decode(<<"openssh-key-v1",0,
                       >>, Pwd) ->
     openssh_key_v1_decode(Rest, N, Pwd, CipherName, KdfName, KdfOptions, N, []).
 
-
 openssh_key_v1_decode(<<?DEC_BIN(BinKey,_L1), Rest/binary>>, I,
                       Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAcc) when I>0 ->
     PublicKey = ssh_message:ssh2_pubkey_decode(BinKey),
     openssh_key_v1_decode(Rest, I-1, Pwd, CipherName, KdfName, KdfOptions, N, [PublicKey|PubKeyAcc]);
-
 openssh_key_v1_decode(<<?DEC_BIN(Encrypted,_L)>>,
                       0, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAccRev) ->
     PubKeys = lists:reverse(PubKeyAccRev),
@@ -1407,7 +1403,6 @@ openssh_key_v1_decode(<<?DEC_BIN(Encrypted,_L)>>,
             error({decryption, DecryptError})
     end.
 
-
 openssh_key_v1_decode_priv_keys(Bin, I, N, KeyAcc, CmntAcc) when I>0 ->
     {PrivKey, <<?DEC_BIN(Comment,_Lc),Rest/binary>>} = ssh_message:ssh2_privkey_decode2(Bin),
     openssh_key_v1_decode_priv_keys(Rest, I-1, N, [PrivKey|KeyAcc], [Comment|CmntAcc]);
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 327ea5cb75..9fc5ff8157 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -687,23 +687,23 @@ ssh2_privkey_encode(#'DSAPrivateKey'
     >>;
 
 ssh2_privkey_encode(#'ECPrivateKey'
-                    {version = ecPrivkeyVer1, % Found this in public_key:ec_key/2 ..
+                    {version = Version,
                      parameters = {namedCurve,OID},
                      privateKey = Priv,
-                     publicKey = Pub
-                    }) when OID == ?'id-Ed25519' orelse
-                            OID == ?'id-Ed448' ->
+                     publicKey = Pub})
+  when OID =:= ?'id-Ed25519' orelse OID =:= ?'id-Ed448',
+       Version =:= ecPrivkeyVer1 orelse Version =:= 1 -> % currently used atom and legacy value 1
     {CurveName,_} = oid2ssh_curvename(OID),
     <<?STRING(CurveName),
       ?STRING(Pub),
       ?STRING(Priv)>>;
 
 ssh2_privkey_encode(#'ECPrivateKey'
-                    {version = ecPrivkeyVer1, % Found this in public_key:ec_key/2 ..
+                    {version = Version,
                      parameters = {namedCurve,OID},
                      privateKey = Priv,
-                     publicKey = Q
-                    }) ->
+                     publicKey = Q})
+  when Version =:= ecPrivkeyVer1 orelse Version =:= 1 -> % currently used atom and legacy value 1
     {CurveName,_} = oid2ssh_curvename(OID),
     <<?STRING(CurveName),
       ?STRING(CurveName), % SIC!
diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl
index 03bd97b6e1..2ccf33f6d6 100644
--- a/lib/ssh/test/ssh_pubkey_SUITE.erl
+++ b/lib/ssh/test/ssh_pubkey_SUITE.erl
@@ -88,12 +88,14 @@
          ssh_hostkey_fingerprint_list/1,
 
          chk_known_hosts/1,
-         ssh_hostkey_pkcs8/1
+         ssh_hostkey_pkcs8/1,
+         ec_private_key_version_compat/1
         ]).
 
 -include_lib("common_test/include/ct.hrl").
 -include_lib("public_key/include/public_key.hrl").
 -include("ssh_test_lib.hrl").
+-include_lib("stdlib/include/assert.hrl").
 
 %%%----------------------------------------------------------------
 %%% Common Test interface functions -------------------------------
@@ -110,7 +112,8 @@ all() ->
      {group, ssh_hostkey_fingerprint},
      {group, ssh_public_key_decode_encode},
      {group, pkcs8},
-     chk_known_hosts
+     chk_known_hosts,
+     ec_private_key_version_compat
     ].
 
 
@@ -796,6 +799,32 @@ ssh_openssh_key_long_header(Config) when is_list(Config) ->
     Encoded = ssh_file:encode(Decoded, rfc4716_key),
     Decoded = ssh_file:decode(Encoded, rfc4716_key).
 
+ec_private_key_version_compat(Config) when is_list(Config) ->
+    Keys =
+        [begin
+             try
+                 % with OTP 28: version = ecPrivkeyVer1 (atom) not integer
+                 {Curve, public_key:generate_key({namedCurve, Curve})}
+             catch Error:Reason:Stacktrace ->
+                     ?CT_LOG("SKIP Curve = ~p Error = ~p Reason = ~p~n~p",
+                             [Curve, Error, Reason, Stacktrace]),
+                     skip
+             end
+         end || Curve <- [ed25519, ed448, secp256r1, secp384r1]],
+    case lists:any(fun(I) -> I /= skip end, Keys) of
+        true ->
+            [begin
+                 PrivLegacy = K#'ECPrivateKey'{version = 1}, % OTP-26, OTP-27
+                 Encoded = ssh_message:ssh2_privkey_encode(K),
+                 EncodedLegacy = ssh_message:ssh2_privkey_encode(PrivLegacy),
+                 ?assertEqual(Encoded, EncodedLegacy),
+                 ?CT_LOG("Curve = ~p [OK]", [Curve])
+             end || {Curve, K} <- Keys, K /= skip];
+        false ->
+            ct:fail(no_keys)
+    end,
+    ok.
+
 %%%----------------------------------------------------------------
 %%% Test case helpers
 %%%----------------------------------------------------------------
-- 
2.51.0

openSUSE Build Service is sponsored by