Skip to content

Commit 70ab1fd

Browse files
committed
pqc http
1 parent 1339c8a commit 70ab1fd

24 files changed

Lines changed: 1653 additions & 3 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-pqc</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-pqc-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.validation.BctlsSSLContextFactory;
25+
import org.apache.camel.quarkus.test.support.pqc.certificate.validation.HybridCertificateTrustManager;
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+
HybridCertificateTrustManager trustManager = new HybridCertificateTrustManager();
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 & 1 deletion
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.ConfigProvider;
3137
import org.junit.jupiter.api.Test;
3238
import org.junit.jupiter.params.ParameterizedTest;
@@ -43,9 +49,14 @@
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.DILITHIUM2, formats = {
54+
CertificateFormat.PEM, CertificateFormat.PKCS12 }, password = PqcNginxTestResource.TRUSTSTORE_PASSWORD)
55+
})
4656
@QuarkusTest
47-
@QuarkusTestResource(HttpTestResource.class)
57+
@QuarkusTestResource(PqcNginxTestResource.class)
4858
public class HttpTest extends AbstractHttpTest {
59+
4960
@Override
5061
public String component() {
5162
return "http";
@@ -138,4 +149,18 @@ static Stream<Arguments> proxyProviders() {
138149
arguments("*localhost*", actualPort, host, 200, expectedGroupId, true));
139150
}
140151

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+
141166
}
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.PqcCertificatesUtil;
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 = PqcCertificatesUtil.getCertificatePem(CERT_NAME);
52+
Path keyFile = PqcCertificatesUtil.getPrimaryKeyPem(CERT_NAME);
53+
Path truststoreFile = PqcCertificatesUtil.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>

0 commit comments

Comments
 (0)