Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-75011] Use Apache Mina as ssh transport layer, remove trilead #1022

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 8 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ THE SOFTWARE.
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.jenkins.plugins.mina-sshd-api</groupId>
<artifactId>mina-sshd-api-core</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.plugins.mina-sshd-api</groupId>
<artifactId>mina-sshd-api-scp</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>apache-httpcomponents-client-4-api</artifactId>
Expand Down Expand Up @@ -137,10 +145,6 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-credentials</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>trilead-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.aws-java-sdk</groupId>
<artifactId>aws-java-sdk-ec2</artifactId>
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@
*/
package hudson.plugins.ec2;

import static org.apache.sshd.client.session.ClientSession.REMOTE_COMMAND_WAIT_EVENTS;

import com.amazonaws.AmazonClientException;
import hudson.model.TaskListener;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.SlaveComputer;
import java.io.IOException;
import java.time.Duration;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.scp.client.CloseableScpClient;
import org.apache.sshd.scp.client.ScpClient;
import org.apache.sshd.scp.client.ScpClientCreator;

/**
* {@link ComputerLauncher} for EC2 that wraps the real user-specified {@link ComputerLauncher}.
Expand All @@ -39,6 +49,8 @@
public abstract class EC2ComputerLauncher extends ComputerLauncher {
private static final Logger LOGGER = Logger.getLogger(EC2ComputerLauncher.class.getName());

private static final long timeout = Duration.ofSeconds(10).toMillis();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: make the constant all-caps

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to publish the comment until it was marked as ready for review, I am still learning GitHub.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem 🙂
This should use the timeout from the node anyway: 7680d4e


@Override
public void launch(SlaveComputer slaveComputer, TaskListener listener) {
try {
Expand Down Expand Up @@ -81,4 +93,19 @@
*/
protected abstract void launchScript(EC2Computer computer, TaskListener listener)
throws AmazonClientException, IOException, InterruptedException;

protected int waitCompletion(ClientChannel clientChannel) {
Set<ClientChannelEvent> clientChannelEvents = clientChannel.waitFor(REMOTE_COMMAND_WAIT_EVENTS, timeout);
if (clientChannelEvents.contains(ClientChannelEvent.TIMEOUT)) {
return -1;
} else {
return clientChannel.getExitStatus();
}
}

protected CloseableScpClient createScpClient(ClientSession session) {
ScpClientCreator creator = ScpClientCreator.instance();
ScpClient client = creator.createScpClient(session);
return CloseableScpClient.singleSessionInstance(client);

Check warning on line 109 in src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 98-109 are not covered by tests
}
}
441 changes: 257 additions & 184 deletions src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java

Large diffs are not rendered by default.

431 changes: 252 additions & 179 deletions src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@
*/
package hudson.plugins.ec2.ssh;

import com.trilead.ssh2.ServerHostKeyVerifier;
import java.security.MessageDigest;
import java.util.logging.Logger;

public class HostKeyVerifierImpl implements ServerHostKeyVerifier {
public class HostKeyVerifierImpl {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we refactor the class name as it is no longer implementing the Interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the name as this is a public class.

private static final Logger LOGGER = Logger.getLogger(HostKeyVerifierImpl.class.getName());

private final String console;
Expand All @@ -51,7 +50,6 @@ private String getFingerprint(byte[] serverHostKey) throws Exception {
return buf.toString();
}

@Override
public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey)
throws Exception {
String fingerprint = getFingerprint(serverHostKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package hudson.plugins.ec2.ssh.proxy;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import org.apache.sshd.client.session.ClientProxyConnector;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;

/**
* {@link ClientProxyConnector} that issue an HTTP CONNECT to connect through an HTTP proxy.
*/
public class ProxyCONNECTListener implements ClientProxyConnector {

private static final long timeout = Duration.ofSeconds(10).toMillis();

public final String targetHost;
public final int targetPort;
public final String proxyUser;
public final String proxyPass;

Check warning

Code scanning / Jenkins Security Scan

Jenkins: Plaintext password storage Warning

Field should be reviewed whether it stores a password and is serialized to disk: proxyPass

public ProxyCONNECTListener(String targetHost, int targetPort, String proxyUser, String proxyPass) {
this.targetHost = targetHost;
this.targetPort = targetPort;
this.proxyUser = proxyUser;
this.proxyPass = proxyPass;
}

@Override
public void sendClientProxyMetadata(ClientSession session) throws Exception {
proxyCONNECT(session.getIoSession());
}

public void proxyCONNECT(IoSession ioSession) {
StringBuilder connectRequest = new StringBuilder();

// Based on https://www.rfc-editor.org/rfc/rfc7231#section-4.3.6
connectRequest
.append("CONNECT ")
.append(targetHost)
.append(':')
.append(targetPort)
.append(" HTTP/1.0\r\n");
// Host should be included https://datatracker.ietf.org/doc/html/rfc2616#section-14.23
connectRequest
.append("Host: ")
.append(targetHost)
.append(':')
.append(targetPort)
.append("\r\n");

if ((proxyUser != null) && (proxyPass != null)) {
String credentials = proxyUser + ":" + proxyPass;
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.ISO_8859_1));
connectRequest.append("Proxy-Authorization: Basic ");
connectRequest.append(encoded);
connectRequest.append("\r\n");
}

// End of the header
connectRequest.append("\r\n");

try {
ioSession
.writeBuffer(new ByteArrayBuffer(connectRequest.toString().getBytes(StandardCharsets.US_ASCII)))
.await(timeout);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

Check warning on line 72 in src/main/java/hudson/plugins/ec2/ssh/proxy/ProxyCONNECTListener.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 17-72 are not covered by tests
}
11 changes: 9 additions & 2 deletions src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
*/
package hudson.plugins.ec2.ssh.verifiers;

import com.trilead.ssh2.KnownHosts;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.Serializable;
import java.util.Arrays;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.DigestUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;

/**
* A representation of the SSH key provided by a remote host to verify itself
Expand Down Expand Up @@ -69,7 +71,12 @@
}

public String getFingerprint() {
return KnownHosts.createHexFingerprint(getAlgorithm(), getKey());
try {
byte[] rawFingerprint = DigestUtils.getRawFingerprint(BuiltinDigests.md5.get(), getKey());
return BufferUtils.toHex(':', rawFingerprint).toLowerCase();
} catch (Exception e) {
return "";

Check warning on line 78 in src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 77-78 are not covered by tests
}
}

@Override
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/hudson/plugins/ec2/util/PEMParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package hudson.plugins.ec2.util;

import java.io.IOException;
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;

/**
* Utility class to parse PEM.
*/
public abstract class PEMParser {
private PEMParser() {}

public static KeyPair decodeKeyPair(String pem, String password) throws IOException {
try (org.bouncycastle.openssl.PEMParser pemParser =
new org.bouncycastle.openssl.PEMParser(new StringReader(pem))) {
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();

if (object instanceof PEMEncryptedKeyPair) {
PEMKeyPair decryptedKeyPair = ((PEMEncryptedKeyPair) object)
.decryptKeyPair(new JcePEMDecryptorProviderBuilder().build(password.toCharArray()));
PrivateKey privateKey = converter.getPrivateKey(decryptedKeyPair.getPrivateKeyInfo());
PublicKey publicKey = converter.getPublicKey(decryptedKeyPair.getPublicKeyInfo());
return new KeyPair(publicKey, privateKey);
} else if (object instanceof PrivateKeyInfo) {
PrivateKey privateKey = converter.getPrivateKey((PrivateKeyInfo) object);
PublicKey publicKey = generatePublicKeyFromPrivateKey(privateKey);
return new KeyPair(publicKey, privateKey);
} else if (object instanceof SubjectPublicKeyInfo) {
PublicKey publicKey = converter.getPublicKey((SubjectPublicKeyInfo) object);
return new KeyPair(publicKey, null);
} else if (object instanceof PEMKeyPair) {
SubjectPublicKeyInfo publicKeyInfo = ((PEMKeyPair) object).getPublicKeyInfo();
PrivateKeyInfo privateKeyInfo = ((PEMKeyPair) object).getPrivateKeyInfo();
return new KeyPair(converter.getPublicKey(publicKeyInfo), converter.getPrivateKey(privateKeyInfo));
} else {
throw new IllegalArgumentException(
"Unsupported PEM object type: " + object.getClass().getName());
}
} catch (Exception e) {
throw new IOException("Failed to parse PEM input", e);
}
}

private static PublicKey generatePublicKeyFromPrivateKey(PrivateKey privateKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(privateKey.getAlgorithm());

if ("RSA".equalsIgnoreCase(privateKey.getAlgorithm())) {
RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec =
keyFactory.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
return keyFactory.generatePublic(rsaPrivateCrtKeySpec);
} else if ("EC".equalsIgnoreCase(privateKey.getAlgorithm())) {
ECPrivateKeySpec ecPrivateKeySpec = keyFactory.getKeySpec(privateKey, ECPrivateKeySpec.class);
return keyFactory.generatePublic(ecPrivateKeySpec);
} else {
return null;
}
} catch (Exception e) {

Check warning on line 70 in src/main/java/hudson/plugins/ec2/util/PEMParser.java

View check run for this annotation

ci.jenkins.io / SpotBugs

REC_CATCH_EXCEPTION

NORMAL: Exception is caught when Exception is not thrown in hudson.plugins.ec2.util.PEMParser.generatePublicKeyFromPrivateKey(PrivateKey)
Raw output
<p> This method uses a try-catch block that catches Exception objects, but Exception is not thrown within the try block, and RuntimeException is not explicitly caught. It is a common bug pattern to say try { ... } catch (Exception e) { something } as a shorthand for catching a number of types of exception each of whose catch blocks is identical, but this construct also accidentally catches RuntimeException as well, masking potential bugs. </p> <p>A better approach is to either explicitly catch the specific exceptions that are thrown, or to explicitly catch RuntimeException exception, rethrow it, and then catch all non-Runtime Exceptions, as shown below:</p> <pre><code>try { ... } catch (RuntimeException e) { throw e; } catch (Exception e) { ... deal with all non-runtime exceptions ... } </code></pre>
return null;

Check warning on line 71 in src/main/java/hudson/plugins/ec2/util/PEMParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 25-71 are not covered by tests
}
}
}
122 changes: 122 additions & 0 deletions src/test/java/hudson/plugins/ec2/HostKeyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package hudson.plugins.ec2;

import static org.junit.Assert.assertEquals;

import hudson.plugins.ec2.ssh.verifiers.HostKey;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class HostKeyTest {

public static String PUBLIC_KEY_SSH_DSS_1024 =
"AAAAB3NzaC1kc3MAAACBAMsQrriFgun2KVgmsGd8drsplZLXyU8uU6r90aIZ+evRpxvoLCJf317Wnu5qVBCzGgEZ8iygYB0bDB/JFch+UVgtyXGH358ClJCDDgNWdOSogTl2gCF+W+8KoRSF+i3ObnEPOTa2akByP5FDzOO+mruVPl8kg8NHYcadCtJizRjhAAAAFQCV9uGT1Mchfbm6uFxEmZf09DwjSQAAAIAyyLw64QIHel17rzdyMyvepkvW4q64WYb7xCVLffaYJA8x1pxHtH4Mmmm0fGG7GFgdnCeD95524CYZR7TDhzKFGcEX607qKg0v5sXs6z8U8lGOeARq/IXQphb7YPZ9PdKUIuJImQEXriI0p5G7aGMmSYjnyEpKhUsM12xpDb2qBAAAAIBbIZPuZzBbbeesmzmGoG63w0tFc+tpPV3lNkAeYYcWpVhpSdHGFatr1lU+8LNT6OXekV2CFyF5kuuYw/B3OFkmHasURnT1+yC49OEpSzA3KOtQzqO2BZqIxDG/IEajKtSPGOWWPaVrHdgDXo3EZ6yCJtCiOMxW5Xz3fiUufp1sdQ==";

public static String PUBLIC_KEY_SSH_RSA_1024 =
"AAAAB3NzaC1yc2EAAAADAQABAAAAgQC8nIRjuQr9gGfGTdq7BL4l3s4n4qEe3w7imhv1cT5cy2HT7DXvnA2gGVmFn4izbWlFlQG1lrtMgIiiwXH/shRx+2FnqayNsOmRJ37TiA0ICjkOrdR4JaYWRafQ0TEC0+EdHl+3iJYOhw9scFpJ2M9kB6W5HJsf4gmXoGGz8SsfsQ==";

public static String PUBLIC_KEY_SSH_RSA_2048 =
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCjw8Wgl1usvj9LCzF1c8PufEIG11V2PHCDNlYc66ccIiojQX79st1Lbp0BJXsa2bvZLYjfqyYP5gqkX7jLslmXPN+Vew91sRTmXJTlANlm/fChHg+Fq+lQK0IKGBIn9RlPDFH+NNoUIw4LbZ4etRJuOfMwiVKsOVOYuuLjiIJTkda9eS9zrhTRUXhUuMIxBLdeJEAYve6oBpcnTKpUbTV+DYlru3Yh6lSIevhA361s65oJauNHFQLQ7Ysi9apiF5hmqt1sThv/NPM/xLwlPrSGqKZWnclJbBKaWFlCijuM7W3Q5zbcdmtvKhxEJMobu+KMbt/LVhV7kD3BBLhADKnZ";

public static String PUBLIC_KEY_SSH_RSA_3072 =
"AAAAB3NzaC1yc2EAAAADAQABAAABgQDzvqmjwxE3UgKOVZDoji9npAu7Uee47sdS60cTN0yx3Aj+c5IznoBLDYt7HwUcjKoj6soRJALFMGvrKe6n4H1+9jF5vrstMB40Ga8858wweehIAEzw/ONAORZdHA2y0WG8K3+bNOVSeXZwASsjbKrYcdotsZarhQtGVks6xQwd7qXUD44DDdFuWsuj5//hSSYSIgjJE3gAfeI2qVoe6Cl6gTGoK9zd+hNrYDehpN7bDgX45ulcaMw7N2kLf+Sg5QqOYL3Xdav/SeNEefNUyE058uRK8Br3WhZh5BJ2qFjzUYe20cFKHJ3gqKiY+8aor6YrDAS5AOEAEdCw1GWHJutGeApouTSqpNZf1uHspKEgLCUu6gb+i14k3YGSqUW/3fRdqmtN5qBYGvOoqgUEG1wlsxjf6lvJxSh6551MEiM3dpXBq3wniFjK56pj9nVjW0erJsOXPIqh9KeQL1dB+fzNX0r3oAog3EEl+x+V/YLE12b8MR9qnaZwyxWPGKoRE70=";

public static String PUBLIC_KEY_SSH_RSA_4096 =
"AAAAB3NzaC1yc2EAAAADAQABAAACAQCvoE/zGFhSYP/aXLGYl27P+Bq5KJ0E53Er163GJRZ119kgPTB17JOKEG1k25tmspoNYVVaSIM81zBi4RUIrP7ft+1wj2FlsMchrEHlrqR31HCCsPmf/YGgzaBBgL2KDNHEsnxIzyZTsY4ZGPS4LZMP8McUXfwvOFkVs12AUNH5hrB0SgMv6sor1VyW43p6u1o1w9MX5omANopOv+Rqm7In0UXNmOocOhOFYqDJVKt05+fI+fduHIwO4Wi3e0K1jK1EmC9YlIJJIz0Ce1+CyGK0Cm7lHj+W2Ea5tERO0DsK/etGbn1w8NcW9XmPVzO4vSvsMm7XrL0hIdNQZKSxas4NNwxr0TZN70T+H3WKRK9VAxCEp5IdahsSevKyrcsRnKX3mcemqJZZ+ODAarPdHemNacywzoaEt2AOSOl1PcW/sA49R4yMYHj8RS6xDv9jeA4Vogj58ynqzaB2F4fCkaV4bmgb2vL0Fkw96Tvq0+Gs902zvtDmnneicCWhNnj+3jRKZjqiQRvA3/BgYrokFGcDra4j9C1vrDVajMitcY+dr0XeA+n9ot29GSx36Fwg3j3QUhamS6/nsKTeIdmEHeym7FT6LKweKL/XcUCs+tkaJFxsJ+S1E+vF2M7SmqkNuB0S17EijZtw01v1zbzocscnfpLXo3UEfBdIe7pjT/IGtw==";

public static String PUBLIC_KEY_ECDSA_SHA2_NISTP256_256 =
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB1ZWbuchcMR2NMc4wu4sB2sFvnxZ45tAyijmSx9pUkQ51InNU7t1qzf2p29VhwdJ7kQSX3HdUcwBP1NfUSEoFw=";

public static String PUBLIC_KEY_ECDSA_SHA2_NISTP384_384 =
"AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEfdfV0luXJ0NJGMpeAMDZvfDjfwpC9U+YDSlgM0Gh2eCCtYCGv41G4tZd5+L1gjPEiS4Y8r+jb3JoAX6JdQfHecK6+NHpZsF0uwrn8zTfA9PT+I9nTtEyBgNWM/v/A5wQ==";

public static String PUBLIC_KEY_ECDSA_SHA2_NISTP521_521 =
"AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFrY2jC8sarrQqI13e9fDhzeUvTFt5j2krHfFfqDrP/M7L5RJzbg4jOSOly7FdOi7JhFkYaEguddhRh2DIUWKHR9ADR9/m4n9WxHR9QaVLUYUyZdQzgdtlY6KfLYJyO5PBSulMhpfDKGoycNKmr6Av1gyESAIBq+bINsgpUby+h9jkC7Q==";

private final String description;

private final String algorithm;

private final String publicKey;

private final String expected;

public HostKeyTest(String description, String algorithm, String publicKey, String expected) {
this.description = description;
this.algorithm = algorithm;
this.publicKey = publicKey;
this.expected = expected;
}

@Before
public void before() {
// Add provider manually to avoid requiring jenkinsrule
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{
"SSH-DSS with key size 1024",
"ssh-dss",
PUBLIC_KEY_SSH_DSS_1024,
"48:0b:8d:f6:6b:8d:99:11:e5:6a:02:9b:fb:f0:20:4e"
},
{
"SSH-RSA with key size 1024",
"ssh-rsa",
PUBLIC_KEY_SSH_RSA_1024,
"17:1d:65:4e:bd:6e:3b:e2:51:46:35:84:db:ff:c2:53"
},
{
"SSH-RSA with key size 2048",
"ssh-rsa",
PUBLIC_KEY_SSH_RSA_2048,
"6a:8c:88:49:9f:fe:47:3e:27:a5:c2:d4:45:6b:28:45"
},
{
"SSH-RSA with key size 3072",
"ssh-rsa",
PUBLIC_KEY_SSH_RSA_3072,
"29:7b:fe:5b:e3:bb:7a:28:9d:41:2a:f3:bf:95:96:2a"
},
{
"SSH-RSA with key size 4096",
"ssh-rsa",
PUBLIC_KEY_SSH_RSA_4096,
"1d:21:8f:0e:97:38:f8:3b:a7:a6:d6:72:f0:c2:ca:20"
},
{
"ECDSA-SHA2-NISTP256 with key size 256",
"ecdsa-sha2-nistp256",
PUBLIC_KEY_ECDSA_SHA2_NISTP256_256,
"a4:59:c0:2b:66:42:24:df:36:ca:d8:55:ae:b9:65:5b"
},
{
"ECDSA-SHA2-NISTP384 with key size 384",
"ecdsa-sha2-nistp384",
PUBLIC_KEY_ECDSA_SHA2_NISTP384_384,
"ec:79:dd:bd:30:26:df:ce:84:5e:83:c1:8b:28:b8:ff"
},
{
"ECDSA-SHA2-NISTP521 with key size 521",
"ecdsa-sha2-nistp521",
PUBLIC_KEY_ECDSA_SHA2_NISTP521_521,
"27:a9:ed:e3:8e:17:00:e1:db:a2:85:e1:f8:ab:f5:60"
}
});
}

@Test
public void testPublicKeyValidation() {
String fingerprint = new HostKey(algorithm, Base64.getDecoder().decode(publicKey)).getFingerprint();
assertEquals(description, expected, fingerprint);
}
}
Loading
Loading