You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kafka.apache.org by sh...@apache.org on 2026/10/07 07:27:33 UTC

(kafka) branch 4.2 updated: KAFKA-20997: Improve KeyFactory loading (#23361) (#23672)

This is an automated email from the ASF dual-hosted git repository.

showuon pushed a commit to branch 4.2
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/4.2 by this push:
     new 10ae2e219f3 KAFKA-20997: Improve KeyFactory loading (#23361) (#23672)
10ae2e219f3 is described below

commit 10ae2e219f3f69253fc76df189d2f7dd0fafcdc0
Author: Maros Orsak <ma...@gmail.com>
AuthorDate: Wed Oct 7 09:27:24 2026 +0200

    KAFKA-20997: Improve KeyFactory loading (#23361) (#23672)
    
    Gracefully handle missing KeyFactory algorithms and only fail when
    trying to use a missing KeyFactory  (cherry picked from commit
    06e2901cb871e8bf5692294cdf7b1caab9b55c77)
    
    Reviewers: Luke Chen <sh...@gmail.com>, Andy Huang
    (github:andyhuangdev)
    
    Co-authored-by: Mickael Maison <mi...@users.noreply.github.com>
---
 .../security/ssl/DefaultSslEngineFactory.java      | 32 ++++++++++++----------
 .../security/ssl/DefaultSslEngineFactoryTest.java  | 26 ++++++++++++++++++
 .../kafka/common/security/ssl/SslFactoryTest.java  | 26 ++++++++++++++++++
 3 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java b/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
index a5068dc83ab..82407e10612 100644
--- a/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
+++ b/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
@@ -48,7 +48,6 @@ import java.security.cert.CertificateFactory;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
 import java.util.Date;
@@ -385,7 +384,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
             try {
                 return Files.getLastModifiedTime(Paths.get(path)).toMillis();
             } catch (IOException e) {
-                log.error("Modification time of key store could not be obtained: " + path, e);
+                log.error("Modification time of key store could not be obtained: {}", path, e);
                 return null;
             }
         }
@@ -424,11 +423,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
     static class PemStore implements SecurityStore {
         private static final PemParser CERTIFICATE_PARSER = new PemParser("CERTIFICATE");
         private static final PemParser PRIVATE_KEY_PARSER = new PemParser("PRIVATE KEY");
-        private static final List<KeyFactory> KEY_FACTORIES = Arrays.asList(
-                keyFactory("RSA"),
-                keyFactory("DSA"),
-                keyFactory("EC")
-        );
+        private static final List<KeyFactory> KEY_FACTORIES = keyFactories(List.of("RSA", "DSA", "EC"));
 
         private final char[] keyPassword;
         private final KeyStore keyStore;
@@ -458,6 +453,18 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
             return false;
         }
 
+        static List<KeyFactory> keyFactories(List<String> algorithms) {
+            List<KeyFactory> keyFactories = new ArrayList<>();
+            for (String algorithm : algorithms) {
+                try {
+                    keyFactories.add(KeyFactory.getInstance(algorithm));
+                } catch (Exception e) {
+                    log.info("Could not create key factory for algorithm {}", algorithm, e);
+                }
+            }
+            return keyFactories;
+        }
+
         private KeyStore createKeyStoreFromPem(String privateKeyPem, String certChainPem, char[] keyPassword) {
             try {
                 KeyStore ks = KeyStore.getInstance("PKCS12");
@@ -521,6 +528,10 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
                 keySpec = keyInfo.getKeySpec(cipher);
             }
 
+            if (KEY_FACTORIES.isEmpty()) {
+                throw new InvalidConfigurationException("No key factories available to load private key");
+            }
+
             InvalidKeySpecException firstException = null;
             for (KeyFactory factory : KEY_FACTORIES) {
                 try {
@@ -533,13 +544,6 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
             throw new InvalidConfigurationException("Private key could not be loaded", firstException);
         }
 
-        private static KeyFactory keyFactory(String algorithm) {
-            try {
-                return KeyFactory.getInstance(algorithm);
-            } catch (Exception e) {
-                throw new InvalidConfigurationException("Could not create key factory for algorithm " + algorithm, e);
-            }
-        }
     }
 
     /**
diff --git a/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java b/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
index ed76495fa03..52be0c4357e 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
@@ -18,21 +18,26 @@ package org.apache.kafka.common.security.ssl;
 
 import org.apache.kafka.common.config.SslConfigs;
 import org.apache.kafka.common.config.types.Password;
+import org.apache.kafka.common.errors.InvalidConfigurationException;
 import org.apache.kafka.test.TestUtils;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.security.KeyFactory;
 import java.security.KeyStore;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 public class DefaultSslEngineFactoryTest {
 
@@ -321,6 +326,27 @@ public class DefaultSslEngineFactoryTest {
         assertNotNull(keyStore.getKey("kafka", KEY_PASSWORD.value().toCharArray()), "Private key not found");
     }
 
+    @Test
+    public void testUnavailableKeyFactoryAlgorithmDoesNotThrow() {
+        List<KeyFactory> factories = DefaultSslEngineFactory.PemStore.keyFactories(List.of("RSA", "DSA", "EC", "BOGUS_FACTORY"));
+        assertFalse(factories.stream().anyMatch(f -> f.getAlgorithm().equals("BOGUS_FACTORY")));
+    }
+
+    @Test
+    public void testPemKeyStoreWithUnparseablePrivateKey() {
+        String bogusKey = "-----BEGIN PRIVATE KEY-----\n"
+                + Base64.getEncoder().encodeToString("not a valid key".getBytes()) + "\n"
+                + "-----END PRIVATE KEY-----";
+
+        assertThrows(InvalidConfigurationException.class, () ->
+                new DefaultSslEngineFactory.PemStore(
+                        new Password(CERTCHAIN),
+                        new Password(bogusKey),
+                        null
+                )
+        );
+    }
+
     private String pemFilePath(String pem) throws Exception {
         return TestUtils.tempFile(pem).getAbsolutePath();
     }
diff --git a/clients/src/test/java/org/apache/kafka/common/security/ssl/SslFactoryTest.java b/clients/src/test/java/org/apache/kafka/common/security/ssl/SslFactoryTest.java
index 1c82dc62ff0..9e888c6ed6c 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/ssl/SslFactoryTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/ssl/SslFactoryTest.java
@@ -44,6 +44,7 @@ import java.security.KeyStore;
 import java.security.Security;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
@@ -361,6 +362,31 @@ public abstract class SslFactoryTest {
         assertNotSame(sslEngineFactory, sslFactory.sslEngineFactory(), "SslEngineFactory not recreated");
     }
 
+    @Test
+    public void testPemReconfigurationWithInvalidKeyPreservesExistingFactory() throws Exception {
+        Properties props = new Properties();
+        props.putAll(sslConfigsBuilder(ConnectionMode.SERVER)
+                .createNewTrustStore(null)
+                .usePem(true)
+                .build());
+        TestSecurityConfig sslConfig = new TestSecurityConfig(props);
+
+        SslFactory sslFactory = new SslFactory(ConnectionMode.SERVER);
+        sslFactory.configure(sslConfig.values());
+        SslEngineFactory sslEngineFactory = sslFactory.sslEngineFactory();
+        assertNotNull(sslEngineFactory, "SslEngineFactory not created");
+
+        // Attempt reconfiguration with a PEM key that no KeyFactory can parse
+        String bogusKey = "-----BEGIN PRIVATE KEY-----\n"
+                + Base64.getEncoder().encodeToString("not a valid key".getBytes()) + "\n"
+                + "-----END PRIVATE KEY-----";
+        props.put(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, new Password(bogusKey));
+        TestSecurityConfig badConfig = new TestSecurityConfig(props);
+
+        assertThrows(ConfigException.class, () -> sslFactory.validateReconfiguration(badConfig.values()));
+        assertSame(sslEngineFactory, sslFactory.sslEngineFactory());
+    }
+
     @Test
     public void testKeyStoreTrustStoreValidation() throws Exception {
         File trustStoreFile = TestUtils.tempFile("truststore", ".jks");