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));
+ }
+ }
+ }
+}