This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch camel-quarkus-main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
The following commit(s) were added to refs/heads/camel-quarkus-main by this
push:
new 1b1f6a40 http-pqc-j21: verify the server certificate instead of
passing curl -k
1b1f6a40 is described below
commit 1b1f6a40aac84fb29bc3816edc84b79cc902cadf
Author: James Netherton <[email protected]>
AuthorDate: Fri Aug 7 07:12:39 2026 +0100
http-pqc-j21: verify the server certificate instead of passing curl -k
The manual testing command in README.adoc passed -k, which disables exactly
the certificate and hostname verification this example exists to
demonstrate.
- README.adoc: use --cacert target/certs/server-cert.pem instead of -k. Also
corrects the sample response body, which showed a JSON payload the route
does not produce.
- CertificateGenerator: add subject alternative names (DNS:localhost,
IP:127.0.0.1) to the server certificate, and write it out as
server-cert.pem so that curl has a CA file to trust. Without a SAN the
certificate only verifies via OpenSSL's legacy CN fallback.
- AbstractPqcTest: stop disabling hostname verification in the RestAssured
and HttpClient 5 clients, so the tests fail if the SAN regresses.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
http-pqc-j21/README.adoc | 16 +++++--
.../pqc/certificates/CertificateGenerator.java | 51 ++++++++++++++++++----
.../java/org/acme/http/pqc/AbstractPqcTest.java | 7 +--
3 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/http-pqc-j21/README.adoc b/http-pqc-j21/README.adoc
index ac2dba23..4b3c629c 100644
--- a/http-pqc-j21/README.adoc
+++ b/http-pqc-j21/README.adoc
@@ -34,6 +34,9 @@ Certificates are automatically generated during application
startup in `target/c
* `server-truststore.p12` - Truststore for validating clients
* `client-keystore.p12` - Client certificate for testing
* `client-truststore.p12` - Client truststore for server validation
+* `server-cert.pem` - Server certificate in PEM form, for clients that cannot
read PKCS12 truststores (for example `curl --cacert`)
+
+The server certificate is issued for `CN=localhost` with subject alternative
names `DNS:localhost` and `IP:127.0.0.1`, so clients can verify the server
identity rather than having to disable verification.
NOTE: Certificates use traditional RSA signatures. The PQC protection comes
from the hybrid key exchange (X25519MLKEM768) negotiated during the TLS
handshake, not from the certificate signatures.
@@ -81,9 +84,10 @@ IMPORTANT: Standard tools like `curl`, `wget`, and `openssl
s_client` do **not**
jdk.tls.namedGroups=X25519MLKEM768,x25519,secp256r1
----
-PKCS12 keystores are automatically generated during test execution in
`target/certs/`:
+Client credentials are automatically generated during test execution in
`target/certs/`:
* `client-keystore.p12` - Client certificate (password: `changeit`)
+* `server-cert.pem` - Server certificate, used by curl to verify the server
Start the application with fallback configuration and test with curl:
@@ -94,10 +98,16 @@ mvn quarkus:dev
# In another terminal
curl --cert target/certs/client-keystore.p12:changeit \
--cert-type P12 \
- -k \
+ --cacert target/certs/server-cert.pem \
https://localhost:xxxxx/pqc/secure
-{"message":"Serving secure data via PQC-enabled TLS connection"}
+Secure data delivered via Post-Quantum Cryptography (PQC)!
+
+Connection details:
+- TLS version: 1.3
+- Key exchange: X25519MLKEM768 (hybrid PQC)
+- Provider: BouncyCastle JSSE
+...
----
NOTE: With fallback enabled, curl will use classical x25519 or secp256r1 key
exchange since it doesn't support X25519MLKEM768. The connection is secure but
not quantum-resistant.
diff --git
a/http-pqc-j21/src/main/java/org/acme/http/pqc/certificates/CertificateGenerator.java
b/http-pqc-j21/src/main/java/org/acme/http/pqc/certificates/CertificateGenerator.java
index cd532afb..17143b22 100644
---
a/http-pqc-j21/src/main/java/org/acme/http/pqc/certificates/CertificateGenerator.java
+++
b/http-pqc-j21/src/main/java/org/acme/http/pqc/certificates/CertificateGenerator.java
@@ -18,6 +18,7 @@ package org.acme.http.pqc.certificates;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -33,9 +34,12 @@ import java.util.Date;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.jboss.logging.Logger;
@@ -100,18 +104,30 @@ public class CertificateGenerator {
/**
* Generates server keystore with RSA certificate.
+ *
+ * <p>
+ * The certificate carries subject alternative names for {@code localhost}
and {@code 127.0.0.1} so that clients
+ * can verify the server identity instead of having to disable hostname
verification. It is also written out as a
+ * PEM file so that it can be passed to tools such as {@code curl
--cacert}.
*/
public static void generateServerKeystore() throws Exception {
- serverData = generateCertificateData("CN=localhost,O=Camel
Quarkus,C=US", true);
+ GeneralNames subjectAltNames = new GeneralNames(new GeneralName[] {
+ new GeneralName(GeneralName.dNSName, "localhost"),
+ new GeneralName(GeneralName.iPAddress, "127.0.0.1") });
+
+ serverData = generateCertificateData("CN=localhost,O=Camel
Quarkus,C=US", true, subjectAltNames);
saveKeyStore(Paths.get(CERT_DIR, "server-keystore.p12"),
serverData.keyPair, serverData.certificate, "server");
LOG.info("Server keystore created: " + CERT_DIR +
"/server-keystore.p12");
+
+ savePem(Paths.get(CERT_DIR, "server-cert.pem"),
serverData.certificate);
+ LOG.info("Server certificate PEM created: " + CERT_DIR +
"/server-cert.pem");
}
/**
* Generates client keystore with RSA certificate.
*/
public static void generateClientKeystore() throws Exception {
- clientData = generateCertificateData("CN=client,O=Camel Quarkus,C=US",
false);
+ clientData = generateCertificateData("CN=client,O=Camel Quarkus,C=US",
false, null);
saveKeyStore(Paths.get(CERT_DIR, "client-keystore.p12"),
clientData.keyPair, clientData.certificate, "client");
LOG.info("Client keystore created: " + CERT_DIR +
"/client-keystore.p12");
}
@@ -135,13 +151,15 @@ public class CertificateGenerator {
/**
* Generates a certificate with RSA keypair.
*
- * @param dn The DN for the certificate subject
- * @param isCA Whether this is a CA certificate
- * @return CertificateData containing keypair and certificate
+ * @param dn The DN for the certificate subject
+ * @param isCA Whether this is a CA certificate
+ * @param subjectAltNames The subject alternative names to add to the
certificate, or {@code null} for none
+ * @return CertificateData containing keypair and
certificate
*/
- private static CertificateData generateCertificateData(String dn, boolean
isCA) throws Exception {
+ private static CertificateData generateCertificateData(String dn, boolean
isCA, GeneralNames subjectAltNames)
+ throws Exception {
KeyPair keyPair = generateKeyPair();
- X509Certificate certificate = generateCertificate(keyPair, dn, isCA);
+ X509Certificate certificate = generateCertificate(keyPair, dn, isCA,
subjectAltNames);
return new CertificateData(keyPair, certificate);
}
@@ -152,7 +170,8 @@ public class CertificateGenerator {
return keyPairGenerator.generateKeyPair();
}
- private static X509Certificate generateCertificate(KeyPair keyPair, String
dn, boolean isCA) throws Exception {
+ private static X509Certificate generateCertificate(KeyPair keyPair, String
dn, boolean isCA,
+ GeneralNames subjectAltNames) throws Exception {
long now = System.currentTimeMillis();
Date notBefore = new Date(now);
// Valid for 3 years for development convenience
@@ -172,6 +191,10 @@ public class CertificateGenerator {
certBuilder.addExtension(Extension.basicConstraints, true, new
BasicConstraints(isCA));
+ if (subjectAltNames != null) {
+ certBuilder.addExtension(Extension.subjectAlternativeName, false,
subjectAltNames);
+ }
+
// Use default SUN provider for RSA signing - no need for BC provider
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA")
.build(keyPair.getPrivate());
@@ -198,6 +221,18 @@ public class CertificateGenerator {
}
}
+ private static void savePem(Path path, X509Certificate cert) throws
Exception {
+ Path dirPath = path.getParent();
+ if (!Files.exists(dirPath)) {
+ Files.createDirectories(dirPath);
+ LOG.info("Created directory: " + dirPath);
+ }
+
+ try (JcaPEMWriter pemWriter = new JcaPEMWriter(new
FileWriter(path.toFile()))) {
+ pemWriter.writeObject(cert);
+ }
+ }
+
private static void saveTrustStore(Path path, X509Certificate cert, String
alias) throws Exception {
Path dirPath = path.getParent();
if (!Files.exists(dirPath)) {
diff --git a/http-pqc-j21/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
b/http-pqc-j21/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
index 30c69ce5..3ff13237 100644
--- a/http-pqc-j21/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
+++ b/http-pqc-j21/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
@@ -34,7 +34,6 @@ import
org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import
org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
-import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HttpResponse;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -76,8 +75,7 @@ abstract class AbstractPqcTest {
.config(RestAssuredConfig.config().sslConfig(
SSLConfig.sslConfig()
.keyStore("target/certs/client-keystore.p12",
"changeit")
-
.trustStore("target/certs/client-truststore.p12", "changeit")
- .allowAllHostnames()))
+
.trustStore("target/certs/client-truststore.p12", "changeit")))
.baseUri("https://localhost:" + RestAssured.port)
.when()
.get("/pqc/secure")
@@ -92,8 +90,7 @@ abstract class AbstractPqcTest {
SSLContext sslContext = createSslContext(securityProvider);
// Create custom SSLConnectionSocketFactory that explicitly sets
named groups
- SSLConnectionSocketFactory sslSocketFactory = new
SSLConnectionSocketFactory(sslContext,
- NoopHostnameVerifier.INSTANCE) {
+ SSLConnectionSocketFactory sslSocketFactory = new
SSLConnectionSocketFactory(sslContext) {
@Override
protected void prepareSocket(javax.net.ssl.SSLSocket socket)
throws java.io.IOException {
super.prepareSocket(socket);