Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions integration-test-groups/http/http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-seda</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-bouncycastle</artifactId>
</dependency>
<!-- can not be test scope only because some helper classes are used in for app prodcers -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-integration-tests-support-pqc-certificate-generator</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down Expand Up @@ -78,6 +87,11 @@
<artifactId>camel-quarkus-integration-tests-support-certificate-generator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
Expand Down Expand Up @@ -153,6 +167,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-bouncycastle-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-seda-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
*/
package org.apache.camel.quarkus.component.http.http;

import javax.net.ssl.SSLContext;

import jakarta.inject.Named;
import org.apache.camel.component.http.HttpClientConfigurer;
import org.apache.camel.quarkus.test.support.pqc.certificate.client.PqcSslClientConfigurer;
import org.apache.camel.quarkus.test.support.pqc.certificate.trustmanager.HybridPqcX509TrustManager;
import org.apache.camel.quarkus.test.support.pqc.certificate.util.BctlsSSLContextFactory;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
Expand All @@ -31,6 +37,7 @@
import static org.apache.camel.quarkus.component.http.common.AbstractHttpResource.USER_ADMIN_PASSWORD;

public class HttpProducers {

@Named
HttpContext basicAuthContext() {
Integer port = ConfigProvider.getConfig().getValue("quarkus.http.test-ssl-port", Integer.class);
Expand All @@ -50,4 +57,18 @@ HttpContext basicAuthContext() {

return context;
}

@Named
public HttpClientConfigurer pqcNginxHttpClientConfigurer() {
try {
// Create SSLContext using BouncyCastle JSSE provider with hybrid certificate validator
// This enables validation of RSA+PQC composite certificates following BC Almanac recommendations
HybridPqcX509TrustManager trustManager = new HybridPqcX509TrustManager();
SSLContext sslContext = BctlsSSLContextFactory.createSSLContext(trustManager);
return new PqcSslClientConfigurer(sslContext);
} catch (Exception e) {
throw new RuntimeException("Failed to create PQC HttpClient configurer", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,15 @@ public String compression() {
.withHeader("Accept-Encoding", "gzip, deflate")
.request(String.class);
}

@Path("/pqc/nginx/tls")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String pqcNginxTls() {
return producerTemplate
.to("direct:pqc-nginx-tls")
.withHeader(Exchange.HTTP_METHOD, "GET")
.request(String.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@ public void configure() throws Exception {
.to("seda:dlq")
.end()
.to("http://localhost:{{quarkus.http.test-port}}/service/common/error");

from("direct:pqc-nginx-tls")
.toF("https://{{pqc.nginx.host}}:{{pqc.nginx.port}}/test?httpClientConfigurer=#pqcNginxHttpClientConfigurer");

// Pure PQC route can not exist - TLS protocol doesn't support pure ML-DSA cipher suites YET
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
import org.apache.camel.quarkus.component.http.common.AbstractHttpTest;
import org.apache.camel.quarkus.component.http.common.HttpTestResource;
import org.apache.camel.quarkus.test.support.certificate.TestCertificates;
import org.apache.camel.quarkus.test.support.pqc.certificate.CertificateFormat;
import org.apache.camel.quarkus.test.support.pqc.certificate.HybridMode;
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCAlgorithm;
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCCertificate;
import org.apache.camel.quarkus.test.support.pqc.certificate.PQCCertificates;
import org.apache.camel.quarkus.test.support.pqc.certificate.PrimaryAlgorithm;
import org.eclipse.microprofile.config.Config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -43,9 +49,15 @@
@TestCertificates(certificates = {
@Certificate(name = HttpTestResource.KEYSTORE_NAME, formats = {
Format.PKCS12 }, password = HttpTestResource.KEYSTORE_PASSWORD) })
@PQCCertificates(certificates = {
@PQCCertificate(name = PqcNginxTestResource.CERT_NAME, hybridMode = HybridMode.CHIMERA, primaryAlgorithm = PrimaryAlgorithm.RSA_2048, pqcAlgorithm = PQCAlgorithm.MLDSA65, formats = {
CertificateFormat.PEM, CertificateFormat.PKCS12 }, password = PqcNginxTestResource.TRUSTSTORE_PASSWORD)
})
@QuarkusTest
@QuarkusTestResource(HttpTestResource.class)
@QuarkusTestResource(PqcNginxTestResource.class)
public class HttpTest extends AbstractHttpTest {

@Override
public String component() {
return "http";
Expand Down Expand Up @@ -137,4 +149,18 @@ static Stream<Arguments> proxyProviders(Config config) {
arguments("*localhost*", actualPort, host, 200, expectedGroupId, true));
}

@Test
public void testPqcNginxTls() {
// Test BCTLS integration with hybrid RSA+Dilithium certificate
// Certificate contains both RSA (for TLS handshake) and Dilithium2 (alternative signature)
// Following BC Almanac Chimera-style composite certificate recommendations
// Note: Dilithium is the legacy name; ML-DSA is the NIST standardized name
RestAssured
.when()
.get("/test/client/http/pqc/nginx/tls")
.then()
.statusCode(200)
.body(is("Hybrid RSA+Dilithium(ML-DSA) certificate validated"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.quarkus.component.http.http.it;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.apache.camel.quarkus.test.support.pqc.certificate.util.CertificatesUtil;
import org.eclipse.microprofile.config.ConfigProvider;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

/**
* Test resource that starts a nginx container with PQC hybrid certificates.
*
* Certificates are generated via @PQCCertificates annotation on the test class.
*/
public class PqcNginxTestResource implements QuarkusTestResourceLifecycleManager {
public static final String TRUSTSTORE_PASSWORD = "changeit";
public static final String CERT_NAME = "nginx-hybrid-pqc";
// Use standard nginx (not OQS) to test BCTLS integration
// OQS-OpenSSL is incompatible with BouncyCastle JSSE at the protocol level
private static final String NGINX_IMAGE = ConfigProvider.getConfig().getValue("nginx.container.image",
String.class);

private GenericContainer<?> container;

@Override
public Map<String, String> start() {
try {
Path certFile = CertificatesUtil.getCertificatePem(CERT_NAME);
Path keyFile = CertificatesUtil.getPrimaryKeyPem(CERT_NAME);
Path truststoreFile = CertificatesUtil.getTruststore(CERT_NAME);

if (!certFile.toFile().exists() || !keyFile.toFile().exists()) {
throw new IllegalStateException(
"PQC certificate files not found at expected locations: cert=" + certFile + ", key=" + keyFile);
}

// Write nginx configuration
// Certificate contains both RSA (primary) and Dilithium2/ML-DSA (alternative) keys
// Use filenames from the certificate files
String certFileName = certFile.getFileName().toString();
String keyFileName = keyFile.getFileName().toString();

String nginxConfig = String.format("""
server {
listen 4433 ssl;
server_name localhost;
ssl_certificate /certs/%s;
ssl_certificate_key /certs/%s;
ssl_protocols TLSv1.3;
location /test {
return 200 "Hybrid RSA+Dilithium(ML-DSA) certificate validated";
add_header Content-Type text/plain;
}
}
""", certFileName, keyFileName);

// Get certificate directory from actual certificate path
Path certDirPath = certFile.getParent();
File nginxConfigFile = certDirPath.resolve("default.conf").toFile();
Files.writeString(nginxConfigFile.toPath(), nginxConfig);

// Start standard nginx container
container = new GenericContainer<>(DockerImageName.parse(NGINX_IMAGE))
.withExposedPorts(4433)
.withFileSystemBind(certDirPath.toAbsolutePath().toString(), "/certs", BindMode.READ_ONLY)
.withFileSystemBind(nginxConfigFile.getAbsolutePath(), "/etc/nginx/conf.d/default.conf",
BindMode.READ_ONLY)
.withLogConsumer(frame -> System.out.print(frame.getUtf8String()))
.waitingFor(Wait.forListeningPort());

container.start();

// Return configuration properties using paths from certificate files
Map<String, String> result = new LinkedHashMap<>();
result.put("pqc.nginx.host", container.getHost());
result.put("pqc.nginx.port", String.valueOf(container.getMappedPort(4433)));
result.put("pqc.nginx.truststore.path", "file://" + truststoreFile.toAbsolutePath());
result.put("pqc.nginx.truststore.password", TRUSTSTORE_PASSWORD);

return result;
} catch (Exception e) {
throw new RuntimeException("Failed to start PQC nginx test resource", e);
}
}

@Override
public void stop() {
if (container != null) {
container.stop();
}
}
}
1 change: 1 addition & 0 deletions integration-tests-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<module>kafka</module>
<module>messaging</module>
<module>mongodb</module>
<module>pqc-certificate-generator</module>
<module>process-executor-support</module>
<module>sftp</module>
<module>splunk</module>
Expand Down
108 changes: 108 additions & 0 deletions integration-tests-support/pqc-certificate-generator/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

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.

-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.camel.quarkus</groupId>
<version>3.35.0-SNAPSHOT</version>
<artifactId>camel-quarkus-integration-tests-support</artifactId>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>camel-quarkus-integration-tests-support-pqc-certificate-generator</artifactId>
<name>Camel Quarkus :: Integration Tests :: Support :: PQC Certificate Generator</name>
<description>Post-Quantum Cryptography certificate generation support for integration tests</description>

<dependencies>
<!-- BouncyCastle dependencies -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
</dependency>

<!-- Quarkus -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>

<!-- JUnit5 for BeforeAllCallback extension -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<scope>compile</scope>
</dependency>

<!-- Testcontainers for docker support (optional) -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<optional>true</optional>
</dependency>

<!-- Apache HttpClient for PqcSslClientConfigurer -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>

<!-- Camel HTTP for HttpClientConfigurer interface -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http</artifactId>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Loading
Loading