You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gf...@apache.org on 2026/05/07 19:03:02 UTC
(camel-quarkus) 02/04: feat: Add Camel SFTP Host Certificate Verification
This is an automated email from the ASF dual-hosted git repository.
gansheer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit ed3e429ceaf6554defe62a99edb92c53177b44b1
Author: Gaelle Fournier <ga...@gmail.com>
AuthorDate: Thu Apr 16 18:36:35 2026 +0200
feat: Add Camel SFTP Host Certificate Verification
---
.../component/ftp/deployment/FtpProcessor.java | 17 +++
.../support/sftp/SftpHostCertTestResource.java | 170 +++++++++++++++++++++
.../quarkus/component/sftp/it/SftpResource.java | 114 ++++++++++++++
.../ftp/src/main/resources/application.properties | 2 +-
.../main/resources/certs/generate-certificates.sh | 66 ++++++--
.../ftp/src/main/resources/certs/host-ca.pub | 1 +
.../src/main/resources/certs/host-key-rsa-cert.pub | 1 +
.../ftp/src/main/resources/certs/host-key-rsa.key | 27 ++++
.../src/main/resources/certs/test-key-rsa-cert.pub | 2 +-
.../ftp/src/main/resources/certs/test-key-rsa.key | 48 +++---
.../quarkus/component/sftp/it/SftpHostCertIT.java | 18 +--
.../component/sftp/it/SftpHostCertTest.java | 78 ++++++++++
12 files changed, 493 insertions(+), 51 deletions(-)
diff --git a/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java b/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java
index 218c96e6f6..967123e502 100644
--- a/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java
+++ b/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java
@@ -18,6 +18,7 @@ package org.apache.camel.quarkus.component.ftp.deployment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
class FtpProcessor {
@@ -27,4 +28,20 @@ class FtpProcessor {
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
+
+ @BuildStep
+ ReflectiveClassBuildItem registerJSchCertificateClasses() {
+ // JSch OpenSSH certificate support classes for @cert-authority parsing in known_hosts
+ // The quarkus-jsch reflection config is missing several classes that JSch loads dynamically.
+ return ReflectiveClassBuildItem.builder(
+ "com.jcraft.jsch.KeyPairRSA",
+ "com.jcraft.jsch.KeyPairECDSA",
+ "com.jcraft.jsch.KeyPairEd25519",
+ "com.jcraft.jsch.KeyPairEd448",
+ "com.jcraft.jsch.KeyPairDSA",
+ "com.jcraft.jsch.SignatureRSA",
+ "com.jcraft.jsch.SignatureECDSA",
+ "com.jcraft.jsch.jce.SignatureEd25519")
+ .build();
+ }
}
diff --git a/integration-tests-support/sftp/src/main/java/org/apache/camel/quarkus/test/support/sftp/SftpHostCertTestResource.java b/integration-tests-support/sftp/src/main/java/org/apache/camel/quarkus/test/support/sftp/SftpHostCertTestResource.java
new file mode 100644
index 0000000000..1f45db0327
--- /dev/null
+++ b/integration-tests-support/sftp/src/main/java/org/apache/camel/quarkus/test/support/sftp/SftpHostCertTestResource.java
@@ -0,0 +1,170 @@
+/*
+ * 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.test.support.sftp;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.quarkus.test.AvailablePortFinder;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.jboss.logging.Logger;
+
+/**
+ * SFTP test resource that presents host certificates for host certificate verification testing.
+ * This is separate from SftpTestResource to avoid interfering with other tests.
+ */
+public class SftpHostCertTestResource implements QuarkusTestResourceLifecycleManager {
+ private static final Logger LOGGER = Logger.getLogger(SftpHostCertTestResource.class);
+
+ private SshServer sshServer;
+ private Path sftpHome;
+
+ @Override
+ public Map<String, String> start() {
+ try {
+ final int port = AvailablePortFinder.getNextAvailable();
+
+ sftpHome = Files.createTempDirectory("sftp-hostcert-");
+ Path adminHome = sftpHome.resolve("admin");
+ Files.createDirectories(adminHome);
+
+ VirtualFileSystemFactory factory = new VirtualFileSystemFactory();
+ factory.setUserHomeDir("admin", adminHome.toAbsolutePath());
+
+ sshServer = SshServer.setUpDefaultServer();
+ sshServer.setPort(port);
+
+ sshServer.setKeyPairProvider(createHostCertKeyPairProvider());
+
+ sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshServer.setCommandFactory(new ScpCommandFactory());
+ sshServer.setPasswordAuthenticator((username, password, session) -> true);
+ sshServer.setPublickeyAuthenticator((username, key, session) -> true);
+
+ // Add certificate signature factories
+ List<NamedFactory<Signature>> signatureFactories = sshServer.getSignatureFactories();
+ signatureFactories.add(BuiltinSignatures.rsa_cert);
+ signatureFactories.add(BuiltinSignatures.rsaSHA256_cert);
+ signatureFactories.add(BuiltinSignatures.rsaSHA512_cert);
+ signatureFactories.add(BuiltinSignatures.ed25519_cert);
+ sshServer.setSignatureFactories(signatureFactories);
+
+ sshServer.setFileSystemFactory(factory);
+ sshServer.start();
+
+ LOGGER.infof("SFTP server with host certificate started on port %d", port);
+
+ return Collections.singletonMap("camel.sftp.hostcert.test-port", Integer.toString(port));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to start SFTP server with host certificate", e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ if (sshServer != null) {
+ sshServer.stop();
+ LOGGER.info("SFTP server with host certificate stopped");
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Failed to stop SFTP server", e);
+ }
+
+ try {
+ if (sftpHome != null && Files.exists(sftpHome)) {
+ Files.walk(sftpHome)
+ .sorted((a, b) -> -a.compareTo(b))
+ .forEach(path -> {
+ try {
+ Files.delete(path);
+ } catch (Exception e) {
+ LOGGER.warn("Failed to delete SFTPHome file", e);
+ }
+ });
+ }
+ } catch (Exception e) {
+ LOGGER.warnf("Failed to delete sftp home: %s, %s", sftpHome, e);
+ }
+
+ AvailablePortFinder.releaseReservedPorts();
+ }
+
+ /**
+ * Creates a KeyPairProvider that wraps the host private key with the OpenSSH host certificate.
+ */
+ private static KeyPairProvider createHostCertKeyPairProvider() {
+ try {
+ // Load host private key from classpath (works in both JVM and native mode)
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ // Write host key to temp file (FileKeyPairProvider needs a file path)
+ Path tempHostKey = Files.createTempFile("host-key-rsa", ".key");
+ try (var keyStream = classLoader.getResourceAsStream("certs/host-key-rsa.key")) {
+ if (keyStream == null) {
+ throw new IllegalStateException("Host key resource not found: certs/host-key-rsa.key");
+ }
+ Files.write(tempHostKey, keyStream.readAllBytes());
+ }
+
+ FileKeyPairProvider keyProvider = new FileKeyPairProvider(tempHostKey);
+ KeyPair originalKeyPair = keyProvider.loadKeys(null).iterator().next();
+
+ // Load host certificate from classpath
+ String certLine;
+ try (var certStream = classLoader.getResourceAsStream("certs/host-key-rsa-cert.pub")) {
+ if (certStream == null) {
+ throw new IllegalStateException("Host certificate resource not found: certs/host-key-rsa-cert.pub");
+ }
+ certLine = new String(certStream.readAllBytes()).trim();
+ }
+
+ PublicKey certKey = PublicKeyEntry.parsePublicKeyEntry(certLine).resolvePublicKey(null, null, null);
+
+ if (!(certKey instanceof OpenSshCertificate)) {
+ throw new IllegalStateException("Host certificate file does not contain an OpenSSH certificate");
+ }
+
+ // Create a key pair with the certificate as the public key
+ KeyPair certKeyPair = new KeyPair(certKey, originalKeyPair.getPrivate());
+
+ // Clean up temp file
+ Files.deleteIfExists(tempHostKey);
+
+ return KeyPairProvider.wrap(certKeyPair);
+
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load host certificate key pair", e);
+ }
+ }
+}
diff --git a/integration-tests/ftp/src/main/java/org/apache/camel/quarkus/component/sftp/it/SftpResource.java b/integration-tests/ftp/src/main/java/org/apache/camel/quarkus/component/sftp/it/SftpResource.java
index 14de2c50c5..0a890acd93 100644
--- a/integration-tests/ftp/src/main/java/org/apache/camel/quarkus/component/sftp/it/SftpResource.java
+++ b/integration-tests/ftp/src/main/java/org/apache/camel/quarkus/component/sftp/it/SftpResource.java
@@ -225,4 +225,118 @@ public class SftpResource {
TIMEOUT_MS,
String.class);
}
+
+ @Path("/hostcert/create/{fileName}")
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ public Response createFileWithHostCertVerification(@PathParam("fileName") String fileName, String fileContent)
+ throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ String port = context.resolvePropertyPlaceholders("{{camel.sftp.hostcert.test-port}}");
+ String uri = "sftp://admin@localhost:" + port
+ + "/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa&knownHostsFile="
+ + knownHostsFile;
+ producerTemplate.sendBodyAndHeader(uri, fileContent, Exchange.FILE_NAME, fileName);
+ return Response
+ .created(new URI("https://camel.apache.org/"))
+ .build();
+ }
+
+ @Path("/hostcert/get/{fileName}")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getFileWithHostCertVerification(@PathParam("fileName") String fileName) throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ return consumerTemplate.receiveBody(
+ "sftp://admin@localhost:{{camel.sftp.hostcert.test-port}}/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa&knownHostsFile="
+ + knownHostsFile + "&localWorkDirectory=target&fileName=" + fileName,
+ TIMEOUT_MS,
+ String.class);
+ }
+
+ @Path("/hostcert/delete/{fileName}")
+ @DELETE
+ public Response deleteFileWithHostCert(@PathParam("fileName") String fileName) throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ consumerTemplate.receiveBody(
+ "sftp://admin@localhost:{{camel.sftp.hostcert.test-port}}/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa&knownHostsFile="
+ + knownHostsFile + "&delete=true&fileName=" + fileName,
+ TIMEOUT_MS,
+ String.class);
+ return Response.noContent().build();
+ }
+
+ @Path("/hostcertWithAlgorithms/create/{fileName}")
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ public Response createFileWithHostCertAndAlgorithms(@PathParam("fileName") String fileName, String fileContent)
+ throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ producerTemplate.sendBodyAndHeader(
+ "sftp://admin@localhost:{{camel.sftp.hostcert.test-port}}/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&knownHostsFile="
+ + knownHostsFile + "&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256",
+ fileContent,
+ Exchange.FILE_NAME, fileName);
+ return Response
+ .created(new URI("https://camel.apache.org/"))
+ .build();
+ }
+
+ @Path("/hostcertWithAlgorithms/get/{fileName}")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getFileWithHostCertAndAlgorithms(@PathParam("fileName") String fileName) throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ return consumerTemplate.receiveBody(
+ "sftp://admin@localhost:{{camel.sftp.hostcert.test-port}}/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&knownHostsFile="
+ + knownHostsFile
+ + "&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256&localWorkDirectory=target&fileName="
+ + fileName,
+ TIMEOUT_MS,
+ String.class);
+ }
+
+ @Path("/hostcertWithAlgorithms/delete/{fileName}")
+ @DELETE
+ public Response deleteFileWithHostCertAndAlgorithms(@PathParam("fileName") String fileName) throws Exception {
+ String knownHostsFile = createHostCaKnownHostsFile();
+ consumerTemplate.receiveBody(
+ "sftp://admin@localhost:{{camel.sftp.hostcert.test-port}}/sftp?password=admin&strictHostKeyChecking=yes&useUserKnownHostsFile=false&knownHostsFile="
+ + knownHostsFile
+ + "&caSignatureAlgorithms=ssh-ed25519,rsa-sha2-512,rsa-sha2-256&delete=true&fileName="
+ + fileName,
+ TIMEOUT_MS,
+ String.class);
+ return Response.noContent().build();
+ }
+
+ /**
+ * Creates a known_hosts file with @cert-authority entry for the host CA.
+ * This allows the client to verify the server's host certificate.
+ */
+ private String createHostCaKnownHostsFile() throws Exception {
+ String resourcePath = "certs/host-ca.pub";
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ try (InputStream stream = classLoader.getResourceAsStream(resourcePath)) {
+ if (stream == null) {
+ throw new RuntimeException("Failed to load host CA public key from: " + resourcePath);
+ }
+ return processHostCaStream(stream);
+ }
+ }
+
+ private String processHostCaStream(InputStream hostCaStream) throws Exception {
+ String hostCaPubKey = new String(hostCaStream.readAllBytes()).trim();
+ String port = context.resolvePropertyPlaceholders("{{camel.sftp.hostcert.test-port}}");
+
+ // Create known_hosts with @cert-authority entry
+ String knownHostsContent = String.format("@cert-authority [localhost]:%s %s%n", port, hostCaPubKey);
+
+ // Use temp directory instead of "target" which may not exist in native mode runtime
+ java.nio.file.Path knownHostsPath = java.nio.file.Files.createTempFile("known_hosts_hostcert", ".txt");
+ java.nio.file.Files.writeString(knownHostsPath, knownHostsContent);
+
+ return knownHostsPath.toAbsolutePath().toString();
+ }
}
diff --git a/integration-tests/ftp/src/main/resources/application.properties b/integration-tests/ftp/src/main/resources/application.properties
index 9d05789d61..25bfaf1cfa 100644
--- a/integration-tests/ftp/src/main/resources/application.properties
+++ b/integration-tests/ftp/src/main/resources/application.properties
@@ -18,4 +18,4 @@
# Change to INFO level to get insights about commands run on the FTP server
quarkus.log.category."org.apache.ftpserver".level = WARNING
-quarkus.native.resources.includes=certs/test-key-rsa.key,certs/test-key-rsa-cert.pub
+quarkus.native.resources.includes=certs/test-key-rsa.key,certs/test-key-rsa-cert.pub,certs/host-ca.pub,certs/host-key-rsa.key,certs/host-key-rsa-cert.pub
diff --git a/integration-tests/ftp/src/main/resources/certs/generate-certificates.sh b/integration-tests/ftp/src/main/resources/certs/generate-certificates.sh
index 8780f21483..8e9079b3e7 100755
--- a/integration-tests/ftp/src/main/resources/certs/generate-certificates.sh
+++ b/integration-tests/ftp/src/main/resources/certs/generate-certificates.sh
@@ -20,11 +20,17 @@
# Script to generate OpenSSH certificate files for SFTP integration tests
#
# This script creates:
+# USER CERTIFICATES:
# - test-key-rsa.key: RSA private key (2048 bits)
-# - test-key-rsa-cert.pub: OpenSSH certificate signed by a temporary CA
+# - test-key-rsa-cert.pub: OpenSSH user certificate signed by user CA
#
-# The certificate is valid for 52 weeks and can be used for testing
-# certificate-based authentication with the mina-sftp component.
+# HOST CERTIFICATES:
+# - host-ca.pub: Host CA public key (for @cert-authority in known_hosts)
+# - host-key-rsa.key: RSA host private key (2048 bits)
+# - host-key-rsa-cert.pub: OpenSSH host certificate signed by host CA
+#
+# The certificates are valid for 52 weeks and can be used for testing
+# certificate-based authentication with the FTP component.
#
set -e
@@ -33,34 +39,66 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "==================================================================="
-echo "Generating OpenSSH Certificates for MINA SFTP Integration Tests"
+echo "Generating OpenSSH Certificates for FTP Integration Tests"
echo "==================================================================="
echo ""
echo "Cleaning up existing files..."
-rm -f ca-key ca-key.pub test-key-rsa.key test-key-rsa.key.pub test-key-rsa-cert.pub
+rm -f user-ca user-ca.pub test-key-rsa.key test-key-rsa.key.pub test-key-rsa-cert.pub
+rm -f host-ca host-ca.pub host-key-rsa.key host-key-rsa.key.pub host-key-rsa-cert.pub
echo "Cleaned up existing files"
-echo "Generating temporary CA key pair..."
-ssh-keygen -t rsa -b 2048 -f ca-key -N "" -C "test-ca" > /dev/null 2>&1
-echo "Created ca-key and ca-key.pub"
+echo ""
+echo "--- USER CERTIFICATE GENERATION ---"
+echo ""
+
+echo "Generating user CA key pair..."
+ssh-keygen -t rsa -b 2048 -f user-ca -N "" -C "user-ca" > /dev/null 2>&1
+echo "Created user-ca and user-ca.pub"
echo "Generating user RSA key pair..."
ssh-keygen -t rsa -b 2048 -f test-key-rsa.key -N "" -C "test-rsa@test" > /dev/null 2>&1
echo "Created test-key-rsa.key and test-key-rsa.key.pub"
-echo "Signing public key with CA to create certificate..."
-ssh-keygen -s ca-key \
+echo "Signing user public key with user CA to create certificate..."
+ssh-keygen -s user-ca \
-I "test-user" \
- -n testuser \
+ -n admin \
-V +520w \
test-key-rsa.key.pub > /dev/null 2>&1
echo "Created test-key-rsa.key-cert.pub"
-echo "Renaming certificate to test-key-rsa-cert.pub..."
+echo "Renaming user certificate to test-key-rsa-cert.pub..."
mv test-key-rsa.key-cert.pub test-key-rsa-cert.pub
echo "Renamed to test-key-rsa-cert.pub"
+echo ""
+echo "--- HOST CERTIFICATE GENERATION ---"
+echo ""
+
+echo "Generating host CA key pair..."
+ssh-keygen -t ed25519 -f host-ca -N "" -C "host-ca" > /dev/null 2>&1
+echo "Created host-ca and host-ca.pub"
+
+echo "Generating host RSA key pair..."
+ssh-keygen -t rsa -b 2048 -f host-key-rsa.key -N "" -C "sftp-server@localhost" > /dev/null 2>&1
+echo "Created host-key-rsa.key and host-key-rsa.key.pub"
+
+echo "Signing host public key with host CA to create host certificate..."
+ssh-keygen -s host-ca \
+ -I "sftp-server" \
+ -h \
+ -n localhost \
+ -V +520w \
+ host-key-rsa.key.pub > /dev/null 2>&1
+echo "Created host-key-rsa.key-cert.pub"
+
+echo "Renaming host certificate to host-key-rsa-cert.pub..."
+mv host-key-rsa.key-cert.pub host-key-rsa-cert.pub
+echo "Renamed to host-key-rsa-cert.pub"
+
+echo ""
echo "Cleaning up temporary files..."
-rm -f ca-key ca-key.pub test-key-rsa.key.pub
-echo "Removed CA keys and public key"
+rm -f user-ca user-ca.pub test-key-rsa.key.pub
+rm -f host-ca host-key-rsa.key.pub
+echo "Removed CA private keys and public keys"
diff --git a/integration-tests/ftp/src/main/resources/certs/host-ca.pub b/integration-tests/ftp/src/main/resources/certs/host-ca.pub
new file mode 100644
index 0000000000..d0a10f97cf
--- /dev/null
+++ b/integration-tests/ftp/src/main/resources/certs/host-ca.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIITv5DGdGNCNkU+DMJsMsAsFoufh4yO5DB2PK48eUmqq host-ca
diff --git a/integration-tests/ftp/src/main/resources/certs/host-key-rsa-cert.pub b/integration-tests/ftp/src/main/resources/certs/host-key-rsa-cert.pub
new file mode 100644
index 0000000000..79c92a7e12
--- /dev/null
+++ b/integration-tests/ftp/src/main/resources/certs/host-key-rsa-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgqfpKfP1HcEq0OkSV20BryP+RpxsjCw3QYoLiO7G8p7sAAAADAQABAAABAQCktIEA/teg39lFRRPsFNVfbE93LCj7cewNUjZX+xoHWpBWA6FfaM1xBFmx99W0SAq9TwX8RC8yNzZAYOY4HQIwJYP9NNskMXrV4efQ/HFxg/OGETKDLi/bFgGEJPzIUX+vwLw+RVBcH8HhxHbhvCX/TBliZ6StOlIgoYi/2RKKp9uh3EmOU5gHozNz0jdzRPgVVQvgQvrW7E+2PS/GI67IUxElZSaI8qYFslXbw+iyQSdBg+ZqFl8Z9HVQweV/H5w0o1+87lWkaSIQxJccbmTcaub4Q+KWLci4qALE7yKLnVpuPJxe7zNV/uqd7fh85v5pl8MxQasZLduPf2AyoM1rAAAAAAAAAAAAAAA [...]
diff --git a/integration-tests/ftp/src/main/resources/certs/host-key-rsa.key b/integration-tests/ftp/src/main/resources/certs/host-key-rsa.key
new file mode 100644
index 0000000000..a8cff6531f
--- /dev/null
+++ b/integration-tests/ftp/src/main/resources/certs/host-key-rsa.key
@@ -0,0 +1,27 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEApLSBAP7XoN/ZRUUT7BTVX2xPdywo+3HsDVI2V/saB1qQVgOhX2jN
+cQRZsffVtEgKvU8F/EQvMjc2QGDmOB0CMCWD/TTbJDF61eHn0PxxcYPzhhEygy4v2xYBhC
+T8yFF/r8C8PkVQXB/B4cR24bwl/0wZYmekrTpSIKGIv9kSiqfbodxJjlOYB6Mzc9I3c0T4
+FVUL4EL61uxPtj0vxiOuyFMRJWUmiPKmBbJV28PoskEnQYPmahZfGfR1UMHlfx+cNKNfvO
+5VpGkiEMSXHG5k3Grm+EPili3IuKgCxO8ii51abjycXu8zVf7qne34fOb+aZfDMUGrGS3b
+j39gMqDNawAAA9CCNbkKgjW5CgAAAAdzc2gtcnNhAAABAQCktIEA/teg39lFRRPsFNVfbE
+93LCj7cewNUjZX+xoHWpBWA6FfaM1xBFmx99W0SAq9TwX8RC8yNzZAYOY4HQIwJYP9NNsk
+MXrV4efQ/HFxg/OGETKDLi/bFgGEJPzIUX+vwLw+RVBcH8HhxHbhvCX/TBliZ6StOlIgoY
+i/2RKKp9uh3EmOU5gHozNz0jdzRPgVVQvgQvrW7E+2PS/GI67IUxElZSaI8qYFslXbw+iy
+QSdBg+ZqFl8Z9HVQweV/H5w0o1+87lWkaSIQxJccbmTcaub4Q+KWLci4qALE7yKLnVpuPJ
+xe7zNV/uqd7fh85v5pl8MxQasZLduPf2AyoM1rAAAAAwEAAQAAAQBERCqKGpaOL+nSm7iP
+q+zqga6IMw4DdisENHSgz8twi9lyRUvwCzTHqKlyqcnyUL/eyi+taSd0tUyvr1oMnP1ork
+wAOZWw8S88Ekeup8tvZOUdRuh8VbrxIDRdrKT3dEwrsQN0/e66WFFYfcFWe9D1+Xk1/8ZS
+JG+g5cMT3WmhfRpBmIRZT3nHQTdjkrVIsK1dkbgCbxQBX/hKD/nV0AC/HjBbqx7MVvpK4N
+MKy+Prg/JUvy51GtUeQNZw92Gc7osLpFAzh2apgMhK1ZbWyPjrb/mQ/NXVG6ETuW+3ktwh
+qLLGPjwG/eE+UgWqn1LFgHINpD5y8NbuQ7bCkM5ADaF9AAAAgCIkD2uKemGJpnbEHBGVOO
+sRoP5TGDMPwueagpH8JPll6GfQo7NzxZQJttp4FDCmzbbTN6u7Jk7f8UHprhhzk//Lgm+a
+Ds1qGN11WA32MFCUJixtgc3re1W4nGF3BIshv4tScQVpMcBhDBtSJSzeVpHfeB+k2hzkK2
+/TCbMAHjmZAAAAgQDPbPrfiAbSjmCOjgf/aa82D73Rry/wL4d6bZTi2wwxu3HmzuA0G7L8
+qGV9UIEJ5jFHbQq/wP46XpiIESfkg+PmrUGOwZQ22cjpsPzxJzysvK3oYiuXduE8uz+c/F
+HDx1ONNeDB011k3h1JRcquYrU5SMqWnZLF/vnyvqQgywLXNwAAAIEAy0Z0WJFIiMYJTf4N
+Wa79DbsDutVei72QpoAwMlzBouD2YBArxznCsVC3rVKPVBlUTT2oszCfwQLo3fMjZs3yjW
+SrKc2/EDex8drZg+w262mX+bDQZD+PI1LgrpF7L5e5//id4On5ie6cwIc+8RJRelqhrftT
+T0lFiZ4C2JhdrW0AAAAVc2Z0cC1zZXJ2ZXJAbG9jYWxob3N0AQIDBAUG
+-----END OPENSSH PRIVATE KEY-----
diff --git a/integration-tests/ftp/src/main/resources/certs/test-key-rsa-cert.pub b/integration-tests/ftp/src/main/resources/certs/test-key-rsa-cert.pub
index ba21c6c28b..fa138337ce 100644
--- a/integration-tests/ftp/src/main/resources/certs/test-key-rsa-cert.pub
+++ b/integration-tests/ftp/src/main/resources/certs/test-key-rsa-cert.pub
@@ -1 +1 @@
-ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgwsB0wUJYWF7WQayMI3EFz63+rzdGxxmy6QmkXXigkbcAAAADAQABAAABAQCd2V7v8ELetZfmObUn3zP28B44AhcDnOdZHErgl1fe6e2Jmnja4BhdtZAG+XZEmKwlQnWNkRJfDs1/ryfP8xthcZIlklEb/6F2D2zmot5BnY6zzL6XWPtuTny1Ym7iah6KSPv9vS9nc2wcEA9BFu+CYHHWJYskv/PE/hwebae/upKafppCWJ97+Kdkc87Whsd1y2PxhhPPCbH/lOOOmzw2qTyxpYciHsr9NaBfoHRwvTWBdWjk6pYKtw+4gmWOmyAngOh9jcXeJ5pKUhWeJh5fmMrHL/mv/0DtrGQ2fhhrYZjIbkuha5EOFqurnXHjwwBz3Ey8JOhaF9UwQlCvOOWnAAAAAAAAAAAAAAA [...]
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgWeZP/HKtZqoYUuTZBJDuYgLpWoFI881IQ2mL7gMIVrIAAAADAQABAAABAQCQ6auYkjIBrtHexpzxGkfWoDU0IeRiye2HjGKZa7fHRdYYaQBstJXcnPgzcgOY70dzYe5A9WorWhi+33YWrox6k16WeeYREcogRZ12M0hnEZQa74J5yUHDxHGofHrgh4SIPqE/HOukC8X5LGYV5VPD4wabFdBxbpuPLuPcddWMhZCdd9OqXGizk5nwLhIOM7N3A5kDxn2NtyBPt3WsxCM8Y4d+yWR6BeoUzhBDRFx2yw1aNLbj7aoc7sMPOJXgIJDI0nm1mcdmF1FQqnPlG4zmh9G1B7B8cZfQ8kz+Q+GrFGkgPSwRqZ14+CUfymutuJzcU7hBDdfgbBu5UWnade7dAAAAAAAAAAAAAAA [...]
diff --git a/integration-tests/ftp/src/main/resources/certs/test-key-rsa.key b/integration-tests/ftp/src/main/resources/certs/test-key-rsa.key
index 751cc308be..ac6440d774 100644
--- a/integration-tests/ftp/src/main/resources/certs/test-key-rsa.key
+++ b/integration-tests/ftp/src/main/resources/certs/test-key-rsa.key
@@ -1,27 +1,27 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-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
+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
-----END OPENSSH PRIVATE KEY-----
diff --git a/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java b/integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertIT.java
similarity index 69%
copy from extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java
copy to integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertIT.java
index 218c96e6f6..2562a13d33 100644
--- a/extensions/ftp/deployment/src/main/java/org/apache/camel/quarkus/component/ftp/deployment/FtpProcessor.java
+++ b/integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertIT.java
@@ -14,17 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.ftp.deployment;
+package org.apache.camel.quarkus.component.sftp.it;
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
-class FtpProcessor {
-
- private static final String FEATURE = "camel-ftp";
-
- @BuildStep
- FeatureBuildItem feature() {
- return new FeatureBuildItem(FEATURE);
- }
+/**
+ * Integration tests for SFTP host certificate verification in native mode.
+ */
+@QuarkusIntegrationTest
+class SftpHostCertIT extends SftpHostCertTest {
}
diff --git a/integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertTest.java b/integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertTest.java
new file mode 100644
index 0000000000..d60935976f
--- /dev/null
+++ b/integration-tests/ftp/src/test/java/org/apache/camel/quarkus/component/sftp/it/SftpHostCertTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sftp.it;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import io.smallrye.certs.Format;
+import io.smallrye.certs.junit5.Certificate;
+import org.apache.camel.quarkus.test.support.certificate.TestCertificates;
+import org.apache.camel.quarkus.test.support.sftp.SftpHostCertTestResource;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+
+@TestCertificates(certificates = {
+ @Certificate(name = "ftp", formats = {
+ Format.PEM }, password = "password"),
+ @Certificate(name = "ftp", formats = {
+ Format.PKCS12 }, password = "password") })
+@QuarkusTest
+@QuarkusTestResource(SftpHostCertTestResource.class)
+class SftpHostCertTest {
+
+ @Test
+ public void testHostCertificateVerification() {
+ RestAssured.given()
+ .contentType(ContentType.TEXT)
+ .body("Host certificate verification test")
+ .post("/sftp/hostcert/create/hostcert-test.txt")
+ .then()
+ .statusCode(201);
+
+ RestAssured.get("/sftp/hostcert/get/hostcert-test.txt")
+ .then()
+ .statusCode(200)
+ .body(is("Host certificate verification test"));
+
+ RestAssured.delete("/sftp/hostcert/delete/hostcert-test.txt")
+ .then()
+ .statusCode(204);
+ }
+
+ @Test
+ public void testHostCertificateVerificationWithCaSignatureAlgorithms() {
+ // Test host certificate verification with specific CA signature algorithms
+ RestAssured.given()
+ .contentType(ContentType.TEXT)
+ .body("Host cert with CA algorithms test")
+ .post("/sftp/hostcertWithAlgorithms/create/hostcert-algo-test.txt")
+ .then()
+ .statusCode(201);
+
+ RestAssured.get("/sftp/hostcertWithAlgorithms/get/hostcert-algo-test.txt")
+ .then()
+ .statusCode(200)
+ .body(is("Host cert with CA algorithms test"));
+
+ RestAssured.delete("/sftp/hostcertWithAlgorithms/delete/hostcert-algo-test.txt")
+ .then()
+ .statusCode(204);
+ }
+}