diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e1a7db702204..a871d9146bd6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -82,7 +82,8 @@ repos:
^services/console-proxy/rdpconsole/src/test/doc/rdp-key\.pem$|
^systemvm/agent/certs/localhost\.key$|
^systemvm/agent/certs/realhostip\.key$|
- ^test/integration/smoke/test_ssl_offloading\.py$
+ ^test/integration/smoke/test_ssl_offloading\.py$|
+ ^utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest\.java$
- id: end-of-file-fixer
exclude: \.vhd$
- id: file-contents-sorter
diff --git a/pom.xml b/pom.xml
index b4e2ec57f81b..7767d3500525 100644
--- a/pom.xml
+++ b/pom.xml
@@ -161,7 +161,6 @@
5.5.0
2.12.5
2.2.1
- 0.1.55
20231013
1.2
2.7.0
@@ -335,11 +334,6 @@
java-ipv6
${cs.java-ipv6.version}
-
- com.jcraft
- jsch
- ${cs.jsch.version}
-
com.rabbitmq
amqp-client
diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py
index b3e7fd3e42f4..fc60207ed7e8 100644
--- a/test/integration/smoke/test_network.py
+++ b/test/integration/smoke/test_network.py
@@ -2349,7 +2349,7 @@ def _get_ip_address_output(self, ssh):
return '\n'.join(res)
@attr(tags=["advanced", "shared"], required_hardware="true")
- def test_01_deployVMInSharedNetwork(self):
+ def test_01_deployVMInSharedNetworkWithConfigDrive(self):
try:
self.virtual_machine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"],
networkids=[self.shared_network.id, self.isolated_network.id],
diff --git a/utils/pom.xml b/utils/pom.xml
index ee6df9602b8f..92bf145de388 100755
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -78,10 +78,6 @@
org.bouncycastle
bctls-jdk15on
-
- com.jcraft
- jsch
-
org.jasypt
jasypt
diff --git a/utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java
index f25881ca09bd..b02789333323 100644
--- a/utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java
+++ b/utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java
@@ -20,14 +20,20 @@
package com.cloud.utils.ssh;
import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.RSAPublicKey;
+import org.apache.cloudstack.utils.security.CertUtils;
import org.apache.commons.codec.binary.Base64;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.KeyPair;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
public class SSHKeysHelper {
@@ -45,8 +51,8 @@ private static String toHexString(byte[] b) {
public SSHKeysHelper(Integer keyLength) {
try {
- keyPair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, keyLength);
- } catch (JSchException e) {
+ keyPair = CertUtils.generateRandomKeyPair(keyLength);
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
e.printStackTrace();
}
}
@@ -105,17 +111,54 @@ public static String getPublicKeyFromKeyMaterial(String keyMaterial) {
}
public String getPublicKey() {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- keyPair.writePublicKey(baos, "");
+ if (keyPair == null || keyPair.getPublic() == null) {
+ return null;
+ }
+ try {
+ RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- return baos.toString();
+ writeString(buffer,"ssh-rsa");
+ writeBigInt(buffer, rsaPublicKey.getPublicExponent());
+ writeBigInt(buffer, rsaPublicKey.getModulus());
+
+ String base64 = Base64.encodeBase64String(buffer.toByteArray());
+
+ return "ssh-rsa " + base64;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
}
- public String getPrivateKey() {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- keyPair.writePrivateKey(baos);
+ private static void writeString(ByteArrayOutputStream out, String str) throws Exception {
+ byte[] data = str.getBytes(StandardCharsets.UTF_8);
+ out.write(ByteBuffer.allocate(4).putInt(data.length).array());
+ out.write(data);
+ }
+
+ private static void writeBigInt(ByteArrayOutputStream out, BigInteger value) throws Exception {
+ byte[] data = value.toByteArray();
+ out.write(ByteBuffer.allocate(4).putInt(data.length).array());
+ out.write(data);
+ }
- return baos.toString();
+ public String getPrivateKey() {
+ if (keyPair == null || keyPair.getPrivate() == null) {
+ return null;
+ }
+ try {
+ final PemObject pemObject = new PemObject("RSA PRIVATE KEY", keyPair.getPrivate().getEncoded());
+ final StringWriter sw = new StringWriter();
+ try (final PemWriter pw = new PemWriter(sw)) {
+ pw.writeObject(pemObject);
+ }
+ return sw.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
}
}
diff --git a/utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest.java b/utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest.java
index a3daf9154b86..fef1fc4e713c 100644
--- a/utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest.java
+++ b/utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest.java
@@ -19,8 +19,14 @@
package com.cloud.utils.ssh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
import org.junit.Test;
public class SSHKeysHelperTest {
@@ -70,4 +76,56 @@ public void dsaKeyTest() {
assertTrue("fc:6e:ef:31:93:f8:92:2b:a9:03:c7:06:90:f5:ec:bb".equals(fingerprint));
}
+
+ @Test
+ public void getPublicKeyFromKeyMaterialShouldHandleSupportedPrefixes() {
+ assertEquals("ecdsa-sha2-nistp256 AAAA", SSHKeysHelper.getPublicKeyFromKeyMaterial("ecdsa-sha2-nistp256 AAAA comment"));
+ assertEquals("ecdsa-sha2-nistp384 AAAA", SSHKeysHelper.getPublicKeyFromKeyMaterial("ecdsa-sha2-nistp384 AAAA comment"));
+ assertEquals("ecdsa-sha2-nistp521 AAAA", SSHKeysHelper.getPublicKeyFromKeyMaterial("ecdsa-sha2-nistp521 AAAA comment"));
+ assertEquals("ssh-ed25519 AAAA", SSHKeysHelper.getPublicKeyFromKeyMaterial("ssh-ed25519 AAAA comment"));
+ }
+
+ @Test
+ public void getPublicKeyFromKeyMaterialShouldParseBase64EncodedMaterial() {
+ String keyMaterial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITestKeyData comment";
+ String encoded = Base64.getEncoder().encodeToString(keyMaterial.getBytes(StandardCharsets.UTF_8));
+
+ assertEquals("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITestKeyData", SSHKeysHelper.getPublicKeyFromKeyMaterial(encoded));
+ }
+
+ @Test
+ public void getPublicKeyFromKeyMaterialShouldReturnNullForInvalidFormats() {
+ assertNull(SSHKeysHelper.getPublicKeyFromKeyMaterial("not-a-valid-key"));
+ assertNull(SSHKeysHelper.getPublicKeyFromKeyMaterial("ssh-unknown AAAA"));
+ assertNull(SSHKeysHelper.getPublicKeyFromKeyMaterial("ssh-rsa"));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void getPublicKeyFingerprintShouldThrowForInvalidPublicKey() {
+ SSHKeysHelper.getPublicKeyFingerprint("invalid-key-format");
+ }
+
+ @Test
+ public void generatedKeysShouldBeWellFormedAndFingerprintConsistent() {
+ SSHKeysHelper helper = new SSHKeysHelper(2048);
+
+ String publicKey = helper.getPublicKey();
+ String privateKey = helper.getPrivateKey();
+ String fingerprint = helper.getPublicKeyFingerPrint();
+
+ assertNotNull(publicKey);
+ assertTrue(publicKey.startsWith("ssh-rsa "));
+
+ String[] keyParts = publicKey.split(" ");
+ assertEquals(2, keyParts.length);
+
+ assertNotNull(privateKey);
+ assertTrue(privateKey.contains("BEGIN RSA PRIVATE KEY"));
+ assertTrue(privateKey.contains("END RSA PRIVATE KEY"));
+
+ assertNotNull(fingerprint);
+ assertEquals(SSHKeysHelper.getPublicKeyFingerprint(publicKey), fingerprint);
+
+ assertTrue("Legacy MD5 fingerprint should be colon-separated hex", fingerprint.matches("^([0-9a-f]{2}:){15}[0-9a-f]{2}$"));
+ }
}