File 0136-ssl-Relax-requierments-on-keyfile.patch of Package erlang

From c492385a0f077c4d8a2416cd9a15ccbef88e39a9 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Thu, 18 Sep 2025 16:49:40 +0200
Subject: [PATCH] ssl: Relax requierments on keyfile

PR-10046 put to hard requierments on keyfile content.

Closes #10217
Closes #10212
---
 lib/ssl/src/ssl_config.erl                    |  27 ++--
 lib/ssl/test/ssl_api_SUITE.erl                |  49 ++++++-
 .../test/ssl_api_SUITE_data/cert_and_key.pem  | 126 ++++++++++++++++++
 3 files changed, 187 insertions(+), 15 deletions(-)
 create mode 100644 lib/ssl/test/ssl_api_SUITE_data/cert_and_key.pem

diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 79ac6eebb7..e901617211 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -390,13 +390,8 @@ init_private_key(undefined, CertKey, DbHandle) ->
         KeyFile ->
             Password = maps:get(password, CertKey, undefined),
             try
-                {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
-                [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
-                                          PKey =:= 'RSAPrivateKey' orelse
-                                              PKey =:= 'DSAPrivateKey' orelse
-                                              PKey =:= 'ECPrivateKey' orelse
-                                              PKey =:= 'PrivateKeyInfo'
-                             ],
+                {ok, PemEntries} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
+                [PemEntry] = key_entry(PemEntries),
                 public_key:pem_entry_decode(PemEntry, Password)
             catch
                 _:Reason ->
@@ -2179,17 +2174,18 @@ do_handle_cert_file(File, PemCacheName) ->
 handle_key_file(#{keyfile := File} = CertKey, PemCacheName) ->
     case file:read_file(File) of
         {ok, Pem} ->
-            case public_key:pem_decode(Pem) of
+            PemEntries = public_key:pem_decode(Pem),
+            Password = maps:get(password, CertKey, ""),
+            case key_entry(PemEntries) of
                 [KeyEntry] ->
-                    Password = maps:get(password, CertKey, ""),
                     try public_key:pem_entry_decode(KeyEntry, Password) of
                         Key ->
-                            handle_key(PemCacheName, File, Key, [KeyEntry])
+                            handle_key(PemCacheName, File, Key, PemEntries)
                     catch _:_ ->
                             {error, wrong_password}
                     end;
-                Unexpected ->
-                    {error, {unexpected_content, Unexpected}}
+                _ ->
+                    {error, {unexpected_content, {missing_single_key, File}}}
             end;
         {error, _} =  Error ->
             Error
@@ -2197,6 +2193,13 @@ handle_key_file(#{keyfile := File} = CertKey, PemCacheName) ->
 handle_key_file(_,_) ->
     ok.
 
+key_entry(PemEntries) ->
+    [PemEntry || PemEntry = {PKey, _ , _} <- PemEntries,
+                 PKey =:= 'RSAPrivateKey' orelse
+                     PKey =:= 'DSAPrivateKey' orelse
+                     PKey =:= 'ECPrivateKey' orelse
+                     PKey =:= 'PrivateKeyInfo'].
+
 handle_key(PemCacheName, File, Key, Content) ->
     case check_key(Key) of
         ok ->
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index b7a3307fbf..63bd79ba9c 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -197,7 +197,9 @@
          exporter_master_secret_consumed/1,
          legacy_prf/0,
          legacy_prf/1,
-         listen_pem_file_failure/1
+         listen_pem_file_failure/1,
+         same_file_for_key_and_cert/0,
+         same_file_for_key_and_cert/1
         ]).
 
 %% Apply export
@@ -331,7 +333,8 @@ gen_api_tests() ->
      cipher_listing,
      export_key_materials,
      legacy_prf,
-     listen_pem_file_failure
+     listen_pem_file_failure,
+     same_file_for_key_and_cert
     ].
 
 handshake_paus_tests() ->
@@ -3849,6 +3852,7 @@ listen_pem_file_failure(Config) when is_list(Config) ->
     BadDH = filename:join(DataDir, "dHParam-invalid.pem"),
     BaseOpts = [{versions, [Version]}, {protocol, tls_or_dtls(Version)}],
     NoCACerts = filename:join(DataDir, "nocacerts.pem"),
+    CertAndKey = filename:join(DataDir, "cert_and_key.pem"),
     {error, {options, {keyfile, {Key, wrong_password}}}} =
         ssl:listen(0, [{certfile, Cert}, {keyfile, Key}] ++ BaseOpts),
     {error, {options, {certfile, {Key, no_certs}}}} =
@@ -3870,11 +3874,50 @@ listen_pem_file_failure(Config) when is_list(Config) ->
                 ssl:listen(0, ServerOpts ++ BaseOpts ++ [{dhfile, BadDH}]);
         _ ->
             ok
-    end.
+    end,
+    %% Shall not fail to have both cert and key in same file
+    {ok, L} = ssl:listen(0, [{certfile, CertAndKey}, {keyfile, CertAndKey}] ++ BaseOpts),
+    ssl:close(L).
+
+%%--------------------------------------------------------------------
+same_file_for_key_and_cert() ->
+    ["Test that it works to put entity cert (can be entity cert chain also) and key in same file"].
+
+same_file_for_key_and_cert(Config) when is_list(Config) ->
+    SHA = sha256,
+    #{client_config := ClientOpts0, 
+      server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(rsa, [{server_chain,
+                                                                               [[{digest, SHA}],
+                                                                                [{digest, SHA}],
+                                                                                [{digest, SHA}]]},
+                                                                              {client_chain,
+                                                                               [[{digest, SHA}],
+                                                                                [{digest, SHA}],
+                                                                                [{digest, SHA}]]}
+                                                                             ]),
+    ServerCert = proplists:get_value(cert, ServerOpts0),
+    {SKeyType, SKey} = proplists:get_value(key, ServerOpts0),
+    ClientCert = proplists:get_value(cert, ClientOpts0),
+    {CKeyType, CKey} = proplists:get_value(key, ClientOpts0),
+    PrivDir = proplists:get_value(priv_dir, Config),
+    SCertAndKeyFile = filename:join(PrivDir, "server_cert_and_key.pem"),
+    CCertAndKeyFile = filename:join(PrivDir, "client_cert_and_key.pem"),
+    SPemE = [{'Certificate', ServerCert, not_encrypted}, {SKeyType, SKey, not_encrypted}],
+    CPemE = [{'Certificate', ClientCert, not_encrypted}, {CKeyType, CKey, not_encrypted}],
+    ok = file:write_file(SCertAndKeyFile, public_key:pem_encode(SPemE)),
+    ok = file:write_file(CCertAndKeyFile, public_key:pem_encode(CPemE)),
+    ssl_test_lib:basic_test(replace_cerkey_der_with_file(CCertAndKeyFile, ClientOpts0),
+                            replace_cerkey_der_with_file(SCertAndKeyFile, ServerOpts0), Config).
+
 %%--------------------------------------------------------------------
 %% Internal functions
 %%--------------------------------------------------------------------
 
+replace_cerkey_der_with_file(File, Opts0) ->
+   Opts = proplists:delete(key, proplists:delete(cert, Opts0)),
+   [{certfile, File}, {keyfile, File} | Opts].
+
+
 establish_connection(Id, ServerNode, ServerOpts, ClientNode, ClientOpts, Hostname) ->
     Server =
         ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
diff --git a/lib/ssl/test/ssl_api_SUITE_data/cert_and_key.pem b/lib/ssl/test/ssl_api_SUITE_data/cert_and_key.pem
new file mode 100644
index 0000000000..6a3e44421f
--- /dev/null
+++ b/lib/ssl/test/ssl_api_SUITE_data/cert_and_key.pem
@@ -0,0 +1,126 @@
+// %CopyrightBegin%
+//
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// Copyright (c) 2010 IETF Trust and the persons identified as the document authors.  All rights reserved.
+// Copyright Ericsson AB 2025. All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors
+//    may be used to endorse or promote products derived from this software
+//    without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// %CopyrightEnd%
+
+-----BEGIN CERTIFICATE-----
+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
+AAAAABcmOEM=
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MDQCAQAwCwYJYIZIAWUDBAMRBCKAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ
+GhscHR4f
+-----END PRIVATE KEY-----
\ No newline at end of file
-- 
2.51.0

openSUSE Build Service is sponsored by