Skip to content

Commit 9affeaf

Browse files
committed
Hybrid PQC test for http
1 parent 523e800 commit 9affeaf

26 files changed

Lines changed: 1981 additions & 2 deletions

File tree

integration-test-groups/http/http/pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@
5050
<groupId>org.apache.camel.quarkus</groupId>
5151
<artifactId>camel-quarkus-seda</artifactId>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.apache.camel.quarkus</groupId>
55+
<artifactId>camel-quarkus-support-bouncycastle</artifactId>
56+
</dependency>
57+
<!-- can not be test scope only because some helper classes are used in for app prodcers -->
58+
<dependency>
59+
<groupId>org.apache.camel.quarkus</groupId>
60+
<artifactId>camel-quarkus-integration-tests-support-pqc-certificate-generator</artifactId>
61+
</dependency>
5362

5463
<!-- test dependencies -->
5564
<dependency>
@@ -78,6 +87,11 @@
7887
<artifactId>camel-quarkus-integration-tests-support-certificate-generator</artifactId>
7988
<scope>test</scope>
8089
</dependency>
90+
<dependency>
91+
<groupId>org.assertj</groupId>
92+
<artifactId>assertj-core</artifactId>
93+
<scope>test</scope>
94+
</dependency>
8195
</dependencies>
8296
<profiles>
8397
<profile>
@@ -153,6 +167,19 @@
153167
</exclusion>
154168
</exclusions>
155169
</dependency>
170+
<dependency>
171+
<groupId>org.apache.camel.quarkus</groupId>
172+
<artifactId>camel-quarkus-support-bouncycastle-deployment</artifactId>
173+
<version>${project.version}</version>
174+
<type>pom</type>
175+
<scope>test</scope>
176+
<exclusions>
177+
<exclusion>
178+
<groupId>*</groupId>
179+
<artifactId>*</artifactId>
180+
</exclusion>
181+
</exclusions>
182+
</dependency>
156183
<dependency>
157184
<groupId>org.apache.camel.quarkus</groupId>
158185
<artifactId>camel-quarkus-seda-deployment</artifactId>

integration-test-groups/http/http/src/main/java/org/apache/camel/quarkus/component/http/http/HttpProducers.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
*/
1717
package org.apache.camel.quarkus.component.http.http;
1818

19+
import javax.net.ssl.SSLContext;
20+
1921
import jakarta.inject.Named;
22+
import org.apache.camel.component.http.HttpClientConfigurer;
23+
import org.apache.camel.quarkus.test.support.pqc.certificate.client.PqcSslClientConfigurer;
24+
import org.apache.camel.quarkus.test.support.pqc.certificate.trustmanager.HybridPqcX509TrustManager;
25+
import org.apache.camel.quarkus.test.support.pqc.certificate.util.BctlsSSLContextFactory;
2026
import org.apache.hc.client5.http.auth.AuthScope;
2127
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
2228
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
@@ -31,6 +37,7 @@
3137
import static org.apache.camel.quarkus.component.http.common.AbstractHttpResource.USER_ADMIN_PASSWORD;
3238

3339
public class HttpProducers {
40+
3441
@Named
3542
HttpContext basicAuthContext() {
3643
Integer port = ConfigProvider.getConfig().getValue("quarkus.http.test-ssl-port", Integer.class);
@@ -50,4 +57,18 @@ HttpContext basicAuthContext() {
5057

5158
return context;
5259
}
60+
61+
@Named
62+
public HttpClientConfigurer pqcNginxHttpClientConfigurer() {
63+
try {
64+
// Create SSLContext using BouncyCastle JSSE provider with hybrid certificate validator
65+
// This enables validation of RSA+PQC composite certificates following BC Almanac recommendations
66+
HybridPqcX509TrustManager trustManager = new HybridPqcX509TrustManager();
67+
SSLContext sslContext = BctlsSSLContextFactory.createSSLContext(trustManager);
68+
return new PqcSslClientConfigurer(sslContext);
69+
} catch (Exception e) {
70+
throw new RuntimeException("Failed to create PQC HttpClient configurer", e);
71+
}
72+
}
73+
5374
}

integration-test-groups/http/http/src/main/java/org/apache/camel/quarkus/component/http/http/HttpResource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,15 @@ public String compression() {
197197
.withHeader("Accept-Encoding", "gzip, deflate")
198198
.request(String.class);
199199
}
200+
201+
@Path("/pqc/nginx/tls")
202+
@GET
203+
@Produces(MediaType.TEXT_PLAIN)
204+
public String pqcNginxTls() {
205+
return producerTemplate
206+
.to("direct:pqc-nginx-tls")
207+
.withHeader(Exchange.HTTP_METHOD, "GET")
208+
.request(String.class);
209+
}
210+
200211
}

integration-test-groups/http/http/src/main/java/org/apache/camel/quarkus/component/http/http/HttpRoutes.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,10 @@ public void configure() throws Exception {
3232
.to("seda:dlq")
3333
.end()
3434
.to("http://localhost:{{quarkus.http.test-port}}/service/common/error");
35+
36+
from("direct:pqc-nginx-tls")
37+
.toF("https://{{pqc.nginx.host}}:{{pqc.nginx.port}}/test?httpClientConfigurer=#pqcNginxHttpClientConfigurer");
38+
39+
// Pure PQC route can not exist - TLS protocol doesn't support pure ML-DSA cipher suites YET
3540
}
3641
}

integration-test-groups/http/http/src/test/java/org/apache/camel/quarkus/component/http/http/it/HttpTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
import org.apache.camel.quarkus.component.http.common.AbstractHttpTest;
2828
import org.apache.camel.quarkus.component.http.common.HttpTestResource;
2929
import org.apache.camel.quarkus.test.support.certificate.TestCertificates;
30+
import org.apache.camel.quarkus.test.support.pqc.certificate.CertificateFormat;
31+
import org.apache.camel.quarkus.test.support.pqc.certificate.HybridMode;
32+
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCAlgorithm;
33+
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCCertificate;
34+
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCCertificates;
35+
import org.apache.camel.quarkus.test.support.pqc.certificate.PrimaryAlgorithm;
3036
import org.eclipse.microprofile.config.Config;
3137
import org.junit.jupiter.api.Test;
3238
import org.junit.jupiter.params.ParameterizedTest;
@@ -43,9 +49,15 @@
4349
@TestCertificates(certificates = {
4450
@Certificate(name = HttpTestResource.KEYSTORE_NAME, formats = {
4551
Format.PKCS12 }, password = HttpTestResource.KEYSTORE_PASSWORD) })
52+
@PQCCertificates(certificates = {
53+
@PQCCertificate(name = PqcNginxTestResource.CERT_NAME, hybridMode = HybridMode.CHIMERA, primaryAlgorithm = PrimaryAlgorithm.RSA_2048, pqcAlgorithm = PQCAlgorithm.MLDSA65, formats = {
54+
CertificateFormat.PEM, CertificateFormat.PKCS12 }, password = PqcNginxTestResource.TRUSTSTORE_PASSWORD)
55+
})
4656
@QuarkusTest
4757
@QuarkusTestResource(HttpTestResource.class)
58+
@QuarkusTestResource(PqcNginxTestResource.class)
4859
public class HttpTest extends AbstractHttpTest {
60+
4961
@Override
5062
public String component() {
5163
return "http";
@@ -137,4 +149,18 @@ static Stream<Arguments> proxyProviders(Config config) {
137149
arguments("*localhost*", actualPort, host, 200, expectedGroupId, true));
138150
}
139151

152+
@Test
153+
public void testPqcNginxTls() {
154+
// Test BCTLS integration with hybrid RSA+Dilithium certificate
155+
// Certificate contains both RSA (for TLS handshake) and Dilithium2 (alternative signature)
156+
// Following BC Almanac Chimera-style composite certificate recommendations
157+
// Note: Dilithium is the legacy name; ML-DSA is the NIST standardized name
158+
RestAssured
159+
.when()
160+
.get("/test/client/http/pqc/nginx/tls")
161+
.then()
162+
.statusCode(200)
163+
.body(is("Hybrid RSA+Dilithium(ML-DSA) certificate validated"));
164+
}
165+
140166
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.camel.quarkus.component.http.http.it;
18+
19+
import java.io.File;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.util.LinkedHashMap;
23+
import java.util.Map;
24+
25+
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
26+
import org.apache.camel.quarkus.test.support.pqc.certificate.util.CertificatesUtil;
27+
import org.eclipse.microprofile.config.ConfigProvider;
28+
import org.testcontainers.containers.BindMode;
29+
import org.testcontainers.containers.GenericContainer;
30+
import org.testcontainers.containers.wait.strategy.Wait;
31+
import org.testcontainers.utility.DockerImageName;
32+
33+
/**
34+
* Test resource that starts a nginx container with PQC hybrid certificates.
35+
*
36+
* Certificates are generated via @PQCCertificates annotation on the test class.
37+
*/
38+
public class PqcNginxTestResource implements QuarkusTestResourceLifecycleManager {
39+
public static final String TRUSTSTORE_PASSWORD = "changeit";
40+
public static final String CERT_NAME = "nginx-hybrid-pqc";
41+
// Use standard nginx (not OQS) to test BCTLS integration
42+
// OQS-OpenSSL is incompatible with BouncyCastle JSSE at the protocol level
43+
private static final String NGINX_IMAGE = ConfigProvider.getConfig().getValue("nginx.container.image",
44+
String.class);
45+
46+
private GenericContainer<?> container;
47+
48+
@Override
49+
public Map<String, String> start() {
50+
try {
51+
Path certFile = CertificatesUtil.getCertificatePem(CERT_NAME);
52+
Path keyFile = CertificatesUtil.getPrimaryKeyPem(CERT_NAME);
53+
Path truststoreFile = CertificatesUtil.getTruststore(CERT_NAME);
54+
55+
if (!certFile.toFile().exists() || !keyFile.toFile().exists()) {
56+
throw new IllegalStateException(
57+
"PQC certificate files not found at expected locations: cert=" + certFile + ", key=" + keyFile);
58+
}
59+
60+
// Write nginx configuration
61+
// Certificate contains both RSA (primary) and Dilithium2/ML-DSA (alternative) keys
62+
// Use filenames from the certificate files
63+
String certFileName = certFile.getFileName().toString();
64+
String keyFileName = keyFile.getFileName().toString();
65+
66+
String nginxConfig = String.format("""
67+
server {
68+
listen 4433 ssl;
69+
server_name localhost;
70+
ssl_certificate /certs/%s;
71+
ssl_certificate_key /certs/%s;
72+
ssl_protocols TLSv1.3;
73+
location /test {
74+
return 200 "Hybrid RSA+Dilithium(ML-DSA) certificate validated";
75+
add_header Content-Type text/plain;
76+
}
77+
}
78+
""", certFileName, keyFileName);
79+
80+
// Get certificate directory from actual certificate path
81+
Path certDirPath = certFile.getParent();
82+
File nginxConfigFile = certDirPath.resolve("default.conf").toFile();
83+
Files.writeString(nginxConfigFile.toPath(), nginxConfig);
84+
85+
// Start standard nginx container
86+
container = new GenericContainer<>(DockerImageName.parse(NGINX_IMAGE))
87+
.withExposedPorts(4433)
88+
.withFileSystemBind(certDirPath.toAbsolutePath().toString(), "/certs", BindMode.READ_ONLY)
89+
.withFileSystemBind(nginxConfigFile.getAbsolutePath(), "/etc/nginx/conf.d/default.conf",
90+
BindMode.READ_ONLY)
91+
.withLogConsumer(frame -> System.out.print(frame.getUtf8String()))
92+
.waitingFor(Wait.forListeningPort());
93+
94+
container.start();
95+
96+
// Return configuration properties using paths from certificate files
97+
Map<String, String> result = new LinkedHashMap<>();
98+
result.put("pqc.nginx.host", container.getHost());
99+
result.put("pqc.nginx.port", String.valueOf(container.getMappedPort(4433)));
100+
result.put("pqc.nginx.truststore.path", "file://" + truststoreFile.toAbsolutePath());
101+
result.put("pqc.nginx.truststore.password", TRUSTSTORE_PASSWORD);
102+
103+
return result;
104+
} catch (Exception e) {
105+
throw new RuntimeException("Failed to start PQC nginx test resource", e);
106+
}
107+
}
108+
109+
@Override
110+
public void stop() {
111+
if (container != null) {
112+
container.stop();
113+
}
114+
}
115+
}

integration-tests-support/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<module>kafka</module>
6363
<module>messaging</module>
6464
<module>mongodb</module>
65+
<module>pqc-certificate-generator</module>
6566
<module>process-executor-support</module>
6667
<module>sftp</module>
6768
<module>splunk</module>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Licensed to the Apache Software Foundation (ASF) under one or more
5+
contributor license agreements. See the NOTICE file distributed with
6+
this work for additional information regarding copyright ownership.
7+
The ASF licenses this file to You under the Apache License, Version 2.0
8+
(the "License"); you may not use this file except in compliance with
9+
the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
<parent>
23+
<groupId>org.apache.camel.quarkus</groupId>
24+
<version>3.35.0-SNAPSHOT</version>
25+
<artifactId>camel-quarkus-integration-tests-support</artifactId>
26+
<relativePath>../pom.xml</relativePath>
27+
</parent>
28+
29+
<artifactId>camel-quarkus-integration-tests-support-pqc-certificate-generator</artifactId>
30+
<name>Camel Quarkus :: Integration Tests :: Support :: PQC Certificate Generator</name>
31+
<description>Post-Quantum Cryptography certificate generation support for integration tests</description>
32+
33+
<dependencies>
34+
<!-- BouncyCastle dependencies -->
35+
<dependency>
36+
<groupId>org.bouncycastle</groupId>
37+
<artifactId>bcprov-jdk18on</artifactId>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.bouncycastle</groupId>
41+
<artifactId>bcpkix-jdk18on</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.bouncycastle</groupId>
45+
<artifactId>bctls-jdk18on</artifactId>
46+
</dependency>
47+
48+
<!-- Quarkus -->
49+
<dependency>
50+
<groupId>io.quarkus</groupId>
51+
<artifactId>quarkus-arc</artifactId>
52+
</dependency>
53+
54+
<!-- JUnit5 for BeforeAllCallback extension -->
55+
<dependency>
56+
<groupId>org.junit.jupiter</groupId>
57+
<artifactId>junit-jupiter-api</artifactId>
58+
<scope>compile</scope>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.junit.platform</groupId>
62+
<artifactId>junit-platform-commons</artifactId>
63+
<scope>compile</scope>
64+
</dependency>
65+
66+
<!-- Testcontainers for docker support (optional) -->
67+
<dependency>
68+
<groupId>org.testcontainers</groupId>
69+
<artifactId>testcontainers</artifactId>
70+
<optional>true</optional>
71+
</dependency>
72+
73+
<!-- Apache HttpClient for PqcSslClientConfigurer -->
74+
<dependency>
75+
<groupId>org.apache.httpcomponents.client5</groupId>
76+
<artifactId>httpclient5</artifactId>
77+
</dependency>
78+
79+
<!-- Camel HTTP for HttpClientConfigurer interface -->
80+
<dependency>
81+
<groupId>org.apache.camel</groupId>
82+
<artifactId>camel-http</artifactId>
83+
</dependency>
84+
85+
<!-- Logging -->
86+
<dependency>
87+
<groupId>org.jboss.logging</groupId>
88+
<artifactId>jboss-logging</artifactId>
89+
</dependency>
90+
</dependencies>
91+
92+
<build>
93+
<plugins>
94+
<plugin>
95+
<groupId>io.smallrye</groupId>
96+
<artifactId>jandex-maven-plugin</artifactId>
97+
<executions>
98+
<execution>
99+
<id>make-index</id>
100+
<goals>
101+
<goal>jandex</goal>
102+
</goals>
103+
</execution>
104+
</executions>
105+
</plugin>
106+
</plugins>
107+
</build>
108+
</project>

0 commit comments

Comments
 (0)