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

acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new d90d972c03c7 CAMEL-22940 - [camel-milo] Cannot configure certificate 
chain (#21210)
d90d972c03c7 is described below

commit d90d972c03c7c8b01d913d8963964151edd1a686
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Feb 2 10:55:14 2026 +0100

    CAMEL-22940 - [camel-milo] Cannot configure certificate chain (#21210)
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../camel/component/milo/KeyStoreLoader.java       |  23 ++++-
 .../milo/client/MiloClientConfiguration.java       |   1 +
 .../component/milo/server/MiloServerComponent.java |  12 ++-
 .../camel/component/milo/KeyStoreLoaderTest.java   | 105 +++++++++++++++++++++
 4 files changed, 135 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/KeyStoreLoader.java
 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/KeyStoreLoader.java
index 61ddf47e9577..ab2ae48eed98 100644
--- 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/KeyStoreLoader.java
+++ 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/KeyStoreLoader.java
@@ -43,10 +43,12 @@ public class KeyStoreLoader {
     public static class Result {
 
         private final X509Certificate certificate;
+        private final X509Certificate[] certificateChain;
         private final KeyPair keyPair;
 
-        public Result(final X509Certificate certificate, final KeyPair 
keyPair) {
+        public Result(final X509Certificate certificate, final 
X509Certificate[] certificateChain, final KeyPair keyPair) {
             this.certificate = certificate;
+            this.certificateChain = certificateChain;
             this.keyPair = keyPair;
         }
 
@@ -54,6 +56,10 @@ public class KeyStoreLoader {
             return this.certificate;
         }
 
+        public X509Certificate[] getCertificateChain() {
+            return this.certificateChain;
+        }
+
         public KeyPair getKeyPair() {
             return this.keyPair;
         }
@@ -132,14 +138,23 @@ public class KeyStoreLoader {
                 = keyStore.getKey(effectiveKeyAlias, this.keyPassword != null 
? this.keyPassword.toCharArray() : null);
 
         if (privateKey instanceof PrivateKey pk) {
-            final X509Certificate certificate = (X509Certificate) 
keyStore.getCertificate(effectiveKeyAlias);
-            if (certificate == null) {
+            // Load the full certificate chain
+            final java.security.cert.Certificate[] chain = 
keyStore.getCertificateChain(effectiveKeyAlias);
+            if (chain == null || chain.length == 0) {
                 return null;
             }
 
+            // Convert to X509Certificate array
+            final X509Certificate[] certificateChain = new 
X509Certificate[chain.length];
+            for (int i = 0; i < chain.length; i++) {
+                certificateChain[i] = (X509Certificate) chain[i];
+            }
+
+            // The first certificate in the chain is the end-entity certificate
+            final X509Certificate certificate = certificateChain[0];
             final PublicKey publicKey = certificate.getPublicKey();
             final KeyPair keyPair = new KeyPair(publicKey, pk);
-            return new Result(certificate, keyPair);
+            return new Result(certificate, certificateChain, keyPair);
         }
 
         return null;
diff --git 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
index 4c61fd2e3812..b345ad53b2db 100644
--- 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
+++ 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
@@ -480,6 +480,7 @@ public class MiloClientConfiguration implements Cloneable {
         }
 
         builder.setCertificate(result.getCertificate());
+        builder.setCertificateChain(result.getCertificateChain());
         builder.setKeyPair(result.getKeyPair());
     }
 
diff --git 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
index f3fad98863a9..8c4d33179da6 100644
--- 
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
+++ 
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
@@ -440,13 +440,21 @@ public class MiloServerComponent extends DefaultComponent 
{
          * desired, do it explicitly.
          */
         Objects.requireNonNull(result, "Setting a null is not supported. call 
setCertificateManager(null) instead.)");
-        loadServerCertificate(result.getKeyPair(), result.getCertificate());
+        loadServerCertificate(result.getKeyPair(), result.getCertificate(), 
result.getCertificateChain());
     }
 
     /**
      * Server certificate
      */
     public void loadServerCertificate(final KeyPair keyPair, final 
X509Certificate certificate) {
+        loadServerCertificate(keyPair, certificate, new X509Certificate[] { 
certificate });
+    }
+
+    /**
+     * Server certificate with full certificate chain
+     */
+    public void loadServerCertificate(
+            final KeyPair keyPair, final X509Certificate certificate, final 
X509Certificate[] certificateChain) {
         this.certificate = certificate;
         // TODO evaluate migration to CertificateGroup
         //        setCertificateManager(new DefaultCertificateManager(keyPair, 
certificate));
@@ -455,7 +463,7 @@ public class MiloServerComponent extends DefaultComponent {
                 this.certificateGroup.updateCertificate(
                         
NodeIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup,
                         keyPair,
-                        new X509Certificate[] { certificate });
+                        certificateChain);
             } catch (Exception e) {
                 throw new RuntimeCamelException(e);
             }
diff --git 
a/components/camel-milo/src/test/java/org/apache/camel/component/milo/KeyStoreLoaderTest.java
 
b/components/camel-milo/src/test/java/org/apache/camel/component/milo/KeyStoreLoaderTest.java
new file mode 100644
index 000000000000..8c2f8c5da0c7
--- /dev/null
+++ 
b/components/camel-milo/src/test/java/org/apache/camel/component/milo/KeyStoreLoaderTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.milo;
+
+import java.security.cert.X509Certificate;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link KeyStoreLoader} certificate chain loading functionality.
+ */
+public class KeyStoreLoaderTest {
+
+    @Test
+    public void testLoadCertificateChain() throws Exception {
+        final KeyStoreLoader loader = new KeyStoreLoader();
+        loader.setUrl("file:src/test/resources/keystore");
+        loader.setKeyStorePassword("testtest");
+        loader.setKeyPassword("test");
+
+        final KeyStoreLoader.Result result = loader.load();
+
+        assertNotNull(result, "Result should not be null");
+        assertNotNull(result.getCertificate(), "Certificate should not be 
null");
+        assertNotNull(result.getCertificateChain(), "Certificate chain should 
not be null");
+        assertNotNull(result.getKeyPair(), "KeyPair should not be null");
+
+        // The certificate chain should contain at least the end-entity 
certificate
+        assertTrue(result.getCertificateChain().length >= 1,
+                "Certificate chain should contain at least one certificate");
+
+        // The first certificate in the chain should be the same as 
getCertificate()
+        assertEquals(result.getCertificate(), result.getCertificateChain()[0],
+                "First certificate in chain should match getCertificate()");
+    }
+
+    @Test
+    public void testLoadCertificateChainWithAlias() throws Exception {
+        final KeyStoreLoader loader = new KeyStoreLoader();
+        loader.setUrl("file:src/test/resources/keystore");
+        loader.setKeyStorePassword("testtest");
+        loader.setKeyPassword("test");
+        loader.setKeyAlias("test");
+
+        final KeyStoreLoader.Result result = loader.load();
+
+        assertNotNull(result, "Result should not be null");
+        assertNotNull(result.getCertificateChain(), "Certificate chain should 
not be null");
+
+        // Verify the chain is properly loaded
+        X509Certificate[] chain = result.getCertificateChain();
+        assertTrue(chain.length >= 1, "Certificate chain should have at least 
one certificate");
+
+        // Verify all certificates in the chain are valid X509Certificates
+        for (X509Certificate cert : chain) {
+            assertNotNull(cert, "Each certificate in chain should not be 
null");
+            assertNotNull(cert.getSubjectX500Principal(), "Certificate should 
have a subject");
+        }
+    }
+
+    @Test
+    public void testCertificateChainOrder() throws Exception {
+        final KeyStoreLoader loader = new KeyStoreLoader();
+        loader.setUrl("file:src/test/resources/keystore");
+        loader.setKeyStorePassword("testtest");
+        loader.setKeyPassword("test");
+
+        final KeyStoreLoader.Result result = loader.load();
+
+        assertNotNull(result, "Result should not be null");
+
+        X509Certificate[] chain = result.getCertificateChain();
+
+        // If the chain has more than one certificate, verify proper chain 
order
+        // Each certificate should be issued by the next one in the chain
+        if (chain.length > 1) {
+            for (int i = 0; i < chain.length - 1; i++) {
+                X509Certificate subject = chain[i];
+                X509Certificate issuer = chain[i + 1];
+
+                // The issuer of chain[i] should match the subject of 
chain[i+1]
+                assertEquals(subject.getIssuerX500Principal(), 
issuer.getSubjectX500Principal(),
+                        "Certificate at index " + i + " should be issued by 
certificate at index " + (i + 1));
+            }
+        }
+    }
+}

Reply via email to