Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 8 additions & 11 deletions src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.apache.sshd.common.config.keys.OpenSshCertificate;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.services.ec2.model.Instance;
import software.amazon.awssdk.services.ec2.model.InstanceStateName;
Expand Down Expand Up @@ -381,7 +379,7 @@
/**
* Our host key verifier just pick up the right strategy and call its verify method.
*/
private static class ServerKeyVerifierImpl implements ServerKeyVerifier {
static class ServerKeyVerifierImpl implements ServerKeyVerifier {
private final EC2Computer computer;
private final TaskListener listener;

Expand All @@ -392,19 +390,18 @@

@Override
public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) {
String sshAlgorithm = KeyHelper.getSshAlgorithm(serverKey);
if (sshAlgorithm == null) {
return false;
PublicKey usableKey = serverKey;
// Unwrap OpenSSH certificate key into actual public key
if (serverKey instanceof OpenSshCertificate cert) {
// Extract actual signed public key
usableKey = cert.getCertPubKey();
}
SlaveTemplate template = computer.getSlaveTemplate();
try {
AsymmetricKeyParameter parameters = PublicKeyFactory.createKey(serverKey.getEncoded());
byte[] openSSHBytes = OpenSSHPublicKeyUtil.encodePublicKey(parameters);

return template != null
&& template.getHostKeyVerificationStrategy()
.getStrategy()
.verify(computer, new HostKey(sshAlgorithm, openSSHBytes), listener);
.verify(computer, usableKey, listener);

Check warning on line 404 in src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 404 is only partially covered, one branch is missing
} catch (Exception exception) {
// false will trigger a SSHException which is a subclass of IOException.
// Therefore, it is not needed to throw a RuntimeException.
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@
*/
package hudson.plugins.ec2.ssh.verifiers;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.XmlFile;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.plugins.ec2.util.KeyHelper;
import java.io.File;
import java.io.IOException;
import java.security.PublicKey;
import java.util.Map;
import java.util.WeakHashMap;
import jenkins.model.Jenkins;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
import org.bouncycastle.crypto.util.PublicKeyFactory;

/**
* Helper methods to allow loading and saving of host keys for a computer. Verifiers
Expand All @@ -57,6 +64,24 @@
return INSTANCE;
}

/**
* Converts a Java {@link PublicKey} to a {@link HostKey} for SSH verification.
* Uses the key's encoded form and BouncyCastle utilities to produce the SSH format.
*
* @param serverKey the public key to convert
* @return a {@link HostKey} representing the SSH-formatted key, or {@code null} if the algorithm is unsupported
* @throws IOException if the key cannot be processed
*/
@CheckForNull
public HostKey getHostKey(@NonNull PublicKey serverKey) throws IOException {
String sshAlgorithm = KeyHelper.getSshAlgorithm(serverKey);
if (sshAlgorithm == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 78 is only partially covered, one branch is missing
return null;

Check warning on line 79 in src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 79 is not covered by tests
}
AsymmetricKeyParameter parameters = PublicKeyFactory.createKey(serverKey.getEncoded());
return new HostKey(serverKey.getAlgorithm(), OpenSSHPublicKeyUtil.encodePublicKey(parameters));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should not be serverKey.getAlgorithm() but sshAlgorithm. This is causing an issue https://issues.jenkins.io/browse/JENKINS-76077

}

/**
* Retrieve the currently trusted host key for the requested computer, or null if
* no key is currently trusted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import hudson.model.TaskListener;
import hudson.plugins.ec2.EC2Cloud;
import hudson.plugins.ec2.EC2Computer;
import hudson.plugins.ec2.util.KeyHelper;
import java.io.IOException;
import java.security.PublicKey;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -38,6 +40,18 @@
public class NonVerifyingKeyVerificationStrategy extends SshHostKeyVerificationStrategy {
private static final Logger LOGGER = Logger.getLogger(NonVerifyingKeyVerificationStrategy.class.getName());

@Override
public boolean verify(EC2Computer computer, PublicKey serverKey, TaskListener listener) throws Exception {
EC2Cloud.log(
LOGGER,
Level.INFO,
computer.getListener(),
String.format(
"No SSH key verification (%s %s) for connections to %s",
KeyHelper.getSshAlgorithm(serverKey), KeyHelper.getFingerprint(serverKey), computer.getName()));
return true;
}

@Override
public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException {
EC2Cloud.log(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import hudson.plugins.ec2.EC2Cloud;
import hudson.plugins.ec2.EC2Computer;
import hudson.plugins.ec2.InstanceState;
import java.security.PublicKey;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -61,10 +62,23 @@
* that should be checked to see if we trust it by the current verifier.
* @param listener the connection listener to write any output log to
* @return whether the provided HostKey is trusted and the current connection can therefore continue.
* @since 1.12
* @since TODO

Check warning on line 65 in src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategy.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL:
*/
public abstract boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws Exception;

/**
* Check if the given key is valid for the host identifier.
* @param computer the computer this connection is being initiated for
* @param serverKey the key that was transmitted by the remote host for the current connection. This is the key
* that should be checked to see if we trust it by the current verifier.
* @param listener the connection listener to write any output log to
* @return whether the provided HostKey is trusted and the current connection can therefore continue.
* @since 1.12
*/
public boolean verify(EC2Computer computer, PublicKey serverKey, TaskListener listener) throws Exception {
return verify(computer, HostKeyHelper.getInstance().getHostKey(serverKey), listener);
}

public abstract static class SshHostKeyVerificationStrategyDescriptor
extends Descriptor<SshHostKeyVerificationStrategy> {}

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/hudson/plugins/ec2/util/KeyHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
Expand Down Expand Up @@ -152,4 +154,28 @@
Properties.removeThreadOverride(Properties.EMULATE_ORACLE);
}
}

/**
* Computes the MD5 fingerprint of the given server public key.
* The fingerprint is formatted as a colon-separated hexadecimal string.
*
* @param serverKey The server's {@link PublicKey} object for which the fingerprint needs to be computed.
* @return A {@code String} representing the MD5 fingerprint of the given server key in
* colon-separated hexadecimal format, or an empty string if an error occurs during computation.
*/
public static String getFingerprint(@NonNull PublicKey serverKey) {
// Emulate Oracle so that the algorithm returned by
// org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey.getAlgorithm
// is the one expected by org.apache.sshd.common.config.keys.KeyUtils
try {
Properties.setThreadOverride(Properties.EMULATE_ORACLE, true);
// Generate MD5 fingerprint just like ssh-keygen
byte[] rawFingerprint = KeyUtils.getRawFingerprint(BuiltinDigests.md5.get(), serverKey);
return BufferUtils.toHex(':', rawFingerprint).toLowerCase();
Copy link
Member

Choose a reason for hiding this comment

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

Recommend Locale.ROOT as a general practice, though in this case I guess a hex string could not contain I.

} catch (Exception e) {
return "";

Check warning on line 176 in src/main/java/hudson/plugins/ec2/util/KeyHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 175-176 are not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

Is some exception expected here? Seems like it should at least log a warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not expected. It all goes does to implementation of Digest / decoder I guess.
We can add a warning.

} finally {
Properties.removeThreadOverride(Properties.EMULATE_ORACLE);
}
}
}
42 changes: 25 additions & 17 deletions src/test/java/hudson/plugins/ec2/MockEC2Computer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ public class MockEC2Computer extends EC2Computer {

private final EC2AbstractSlave slave;

private final SlaveTemplate slaveTemplate;

public MockEC2Computer(EC2AbstractSlave slave) {
super(slave);
this.slave = slave;
this.slaveTemplate = createSlaveTemplate();
}

// Create a computer
Expand Down Expand Up @@ -70,23 +73,8 @@ public String getEc2Type() {
return new MockEC2Computer(slave);
}

@Override
public String getDecodedConsoleOutput() throws SdkException {
return getConsole();
}

@Override
public InstanceState getState() {
return state;
}

@Override
public EC2AbstractSlave getNode() {
return slave;
}

@Override
public SlaveTemplate getSlaveTemplate() {
// Create a SlaveTemplate
public static SlaveTemplate createSlaveTemplate() {
return new SlaveTemplate(
"ami-123",
EC2AbstractSlave.TEST_ZONE,
Expand Down Expand Up @@ -135,6 +123,26 @@ public SlaveTemplate getSlaveTemplate() {
EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED);
}

@Override
public String getDecodedConsoleOutput() throws SdkException {
return getConsole();
}

@Override
public InstanceState getState() {
return state;
}

@Override
public EC2AbstractSlave getNode() {
return slave;
}

@Override
public SlaveTemplate getSlaveTemplate() {
return slaveTemplate;
}

public void setState(InstanceState state) {
this.state = state;
}
Expand Down
59 changes: 59 additions & 0 deletions src/test/java/hudson/plugins/ec2/ssh/EC2SSHLauncherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package hudson.plugins.ec2.ssh;

import hudson.model.TaskListener;
import hudson.plugins.ec2.HostKeyVerificationStrategyEnum;
import hudson.plugins.ec2.MockEC2Computer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class EC2SSHLauncherTest {

@Rule
public JenkinsRule r = new JenkinsRule();

@Test
public void testServerKeyVerifier() throws Exception {
for (String publicKeyFile : List.of(
"ssh_host_dss_1024.pub",
"ssh_host_rsa_1024.pub",
"ssh_host_rsa_2048.pub",
"ssh_host_rsa_3072.pub",
"ssh_host_rsa_4096.pub",
// Special case of Open SSH Certificate
// ssh-keygen -t rsa -b 2048 -f ./ssh_host_rsa -N ""
// ssh-keygen -s ./ssh_host_rsa -I ec2SshLauncherTest -h -n localhost -V +0s:+999999999s
// ./ssh_host_rsa.pub
"ssh_host_rsa-cert.pub")) {

String sshHostKeyPath = Files.readString(Path.of(getClass()
.getClassLoader()
.getResource("hudson/plugins/ec2/ssh/" + publicKeyFile)
.getPath()))
.trim();

MockEC2Computer computer = MockEC2Computer.createComputer(publicKeyFile);
r.jenkins.addNode(computer.getNode());

computer.getSlaveTemplate().setHostKeyVerificationStrategy(HostKeyVerificationStrategyEnum.OFF);
Assert.assertTrue(new EC2SSHLauncher.ServerKeyVerifierImpl(computer, TaskListener.NULL)
.verifyServerKey(
null,
null,
PublicKeyEntry.parsePublicKeyEntry(sshHostKeyPath).resolvePublicKey(null, null, null)));
computer.getSlaveTemplate().setHostKeyVerificationStrategy(HostKeyVerificationStrategyEnum.ACCEPT_NEW);
Assert.assertTrue(new EC2SSHLauncher.ServerKeyVerifierImpl(computer, TaskListener.NULL)
.verifyServerKey(
null,
null,
PublicKeyEntry.parsePublicKeyEntry(sshHostKeyPath).resolvePublicKey(null, null, null)));

r.jenkins.removeNode(computer.getNode());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-dss AAAAB3NzaC1kc3MAAACBAMsQrriFgun2KVgmsGd8drsplZLXyU8uU6r90aIZ+evRpxvoLCJf317Wnu5qVBCzGgEZ8iygYB0bDB/JFch+UVgtyXGH358ClJCDDgNWdOSogTl2gCF+W+8KoRSF+i3ObnEPOTa2akByP5FDzOO+mruVPl8kg8NHYcadCtJizRjhAAAAFQCV9uGT1Mchfbm6uFxEmZf09DwjSQAAAIAyyLw64QIHel17rzdyMyvepkvW4q64WYb7xCVLffaYJA8x1pxHtH4Mmmm0fGG7GFgdnCeD95524CYZR7TDhzKFGcEX607qKg0v5sXs6z8U8lGOeARq/IXQphb7YPZ9PdKUIuJImQEXriI0p5G7aGMmSYjnyEpKhUsM12xpDb2qBAAAAIBbIZPuZzBbbeesmzmGoG63w0tFc+tpPV3lNkAeYYcWpVhpSdHGFatr1lU+8LNT6OXekV2CFyF5kuuYw/B3OFkmHasURnT1+yC49OEpSzA3KOtQzqO2BZqIxDG/IEajKtSPGOWWPaVrHdgDXo3EZ6yCJtCiOMxW5Xz3fiUufp1sdQ==
27 changes: 27 additions & 0 deletions src/test/resources/hudson/plugins/ec2/ssh/ssh_host_rsa
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEA1qUcUNNcGnZTvneThb9U4nqQdQ0EGc9cJv+W9lUhF7ph2jy2DeYs
HakQHX/nH5CgBFyvUlD7QGAqPC3/65X3h4QWSGH1kEWMq93F9cm33v0b31YPc/qbnw2Ns9
M5yxIz7kd0a5Li0OkUi7ENhgm+jpmYwGw1VE+lMDxUqtpgAYMEcrESzfORR80W/9+iS8PU
peqdbVSWltD+Ze0TeY8UYWtiCqBUnkQ6nqp02Zrfzw4jcQCYRUvWT09b9AfU7nxzUAGGdS
fzdoNicus5QuLO3HIeLfvXM1Ln2KVGuylys1TVcLboc9srL6VNm/Vrc/wYGnQEkKz6G/NR
2NbjZNz79wAAA9i8l634vJet+AAAAAdzc2gtcnNhAAABAQDWpRxQ01wadlO+d5OFv1Tiep
B1DQQZz1wm/5b2VSEXumHaPLYN5iwdqRAdf+cfkKAEXK9SUPtAYCo8Lf/rlfeHhBZIYfWQ
RYyr3cX1ybfe/RvfVg9z+pufDY2z0znLEjPuR3RrkuLQ6RSLsQ2GCb6OmZjAbDVUT6UwPF
Sq2mABgwRysRLN85FHzRb/36JLw9Sl6p1tVJaW0P5l7RN5jxRha2IKoFSeRDqeqnTZmt/P
DiNxAJhFS9ZPT1v0B9TufHNQAYZ1J/N2g2Jy6zlC4s7cch4t+9czUufYpUa7KXKzVNVwtu
hz2ysvpU2b9Wtz/BgadASQrPob81HY1uNk3Pv3AAAAAwEAAQAAAQBcwLybsLn8NWW6yLFW
+ncT5yLFcfpzrMeFkq0JhX/nYQMn61RAbVZi+sub88lMOpLrT6HzUEtCZkvZ9YbBpuxwAw
QeAza6QVOocQ06SRDLq1SADhBfbcwRzymMq5otxo/qR7ZnRfH9lLrsInZVlaBivrzjad8h
r8kSLv/nVLZn8Arsc7c0s9vPH4EXLJKYMT2Y9DMGTJzIRHxTgPG09DZkxpSNTzlLY8VZ/t
FdMGNG9IHRhMiDw1QNlSZ0VAouwb0zZAPuPNBHG4zqmCUV1Da+jG82vEwgmoQ5KTJQmq56
Ao774XgksdCIPb4nM6MhvbU1mAhA97eQOUvsn8haMUYBAAAAgBqfqx+cKzAVHJsOk9ZMHh
YuJjxHbw8fbGTXzIvxTHfVWsQpBf+oziVgT9sBSDaX0Nis0AVNQWyMWxg9OHqJigkRKfJV
8/mqbtnBdjvKdUvDxQ6Pl2MZU6mD9OG9SglWs4LboAksQQUfK9+k5BiAPAuhycpBQQsmzV
m/13NC14P5AAAAgQD4dYj0++uyRaZt4JJALnyMPYwAVgNDTB5TBK58aF/9PKzQ++fIypF1
Wq8NQnzkshq3TFIyTuo8rrdtj2xGJ6TLg5AeXk11aKDkW0asqTL8FxrHIG9eYAoDHFjD8u
wQlqdGNR29FlozpEFiehI56+4qdNqrhJRToSlK6hS1Ho5lPwAAAIEA3SjZHSpyK+0Ttjk2
cWcBqYI+G4zDRKPHvos2cY3Ny2taQHY+apwvlPp4jc84ArZV+5CEgjuyqx6hS712QYR+Kh
D86TnZDzrnOqNP8SNwEj1bjfwWtwuhp4XLrxiJwfYNOZ/TIXl49wUKxFN69ugVPeNh1Axi
xRHsMfrNRcJ1o0kAAAAjYWJ1cmRhamV3aWN6QE1hYy1hYnVyZGFqZXdpY3oubG9jYWw=
-----END OPENSSH PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[email protected] AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgdf6SSoXZeQwvZdumu/h7cd5umrlltC4dhhIWCLMGfy0AAAADAQABAAABAQDWpRxQ01wadlO+d5OFv1TiepB1DQQZz1wm/5b2VSEXumHaPLYN5iwdqRAdf+cfkKAEXK9SUPtAYCo8Lf/rlfeHhBZIYfWQRYyr3cX1ybfe/RvfVg9z+pufDY2z0znLEjPuR3RrkuLQ6RSLsQ2GCb6OmZjAbDVUT6UwPFSq2mABgwRysRLN85FHzRb/36JLw9Sl6p1tVJaW0P5l7RN5jxRha2IKoFSeRDqeqnTZmt/PDiNxAJhFS9ZPT1v0B9TufHNQAYZ1J/N2g2Jy6zlC4s7cch4t+9czUufYpUa7KXKzVNVwtuhz2ysvpU2b9Wtz/BgadASQrPob81HY1uNk3Pv3AAAAAAAAAAAAAAACAAAAEmVjMlNzaExhdW5jaGVyVGVzdAAAAA0AAAAJbG9jYWxob3N0AAAAAGi4HYoAAAAApFLniQAAAAAAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBANalHFDTXBp2U753k4W/VOJ6kHUNBBnPXCb/lvZVIRe6Ydo8tg3mLB2pEB1/5x+QoARcr1JQ+0BgKjwt/+uV94eEFkhh9ZBFjKvdxfXJt979G99WD3P6m58NjbPTOcsSM+5HdGuS4tDpFIuxDYYJvo6ZmMBsNVRPpTA8VKraYAGDBHKxEs3zkUfNFv/fokvD1KXqnW1UlpbQ/mXtE3mPFGFrYgqgVJ5EOp6qdNma388OI3EAmEVL1k9PW/QH1O58c1ABhnUn83aDYnLrOULiztxyHi371zNS59ilRrspcrNU1XC26HPbKy+lTZv1a3P8GBp0BJCs+hvzUdjW42Tc+/cAAAEUAAAADHJzYS1zaGEyLTUxMgAAAQBDgCn5bPxMkYlpNK4EML4kxP2BxiHeMYze3RwpMpIzlbd3nLY8FP5kbdVa/OjQmyxonkus3eyOVePaOZE2Vir4wAxxF9EvTQshaPJ/MXkUPhIFfA1wocgxwNldrS/phrvGMqB18Y6dnw6hxMwsSl5hBEPFiDw3FY6X5o1LnUSrfHy4Ax5HBYouDKCgd6EQ8DYakPzvjunMSoD6rAFPYHpOCFkbXJjcLlnB5/ec38g8mJp43w4hdyf2Vg8Lh594tfD9fLvUxuCgZ6lqDGouetVHGNAKHTpEdbb6DZLk9gmyx8PbrX2uSY+Hm+vDhk6c05KPtsI5bc1o1wNax8Wh/6ru publickeycert
1 change: 1 addition & 0 deletions src/test/resources/hudson/plugins/ec2/ssh/ssh_host_rsa.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWpRxQ01wadlO+d5OFv1TiepB1DQQZz1wm/5b2VSEXumHaPLYN5iwdqRAdf+cfkKAEXK9SUPtAYCo8Lf/rlfeHhBZIYfWQRYyr3cX1ybfe/RvfVg9z+pufDY2z0znLEjPuR3RrkuLQ6RSLsQ2GCb6OmZjAbDVUT6UwPFSq2mABgwRysRLN85FHzRb/36JLw9Sl6p1tVJaW0P5l7RN5jxRha2IKoFSeRDqeqnTZmt/PDiNxAJhFS9ZPT1v0B9TufHNQAYZ1J/N2g2Jy6zlC4s7cch4t+9czUufYpUa7KXKzVNVwtuhz2ysvpU2b9Wtz/BgadASQrPob81HY1uNk3Pv3 publickey
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC8nIRjuQr9gGfGTdq7BL4l3s4n4qEe3w7imhv1cT5cy2HT7DXvnA2gGVmFn4izbWlFlQG1lrtMgIiiwXH/shRx+2FnqayNsOmRJ37TiA0ICjkOrdR4JaYWRafQ0TEC0+EdHl+3iJYOhw9scFpJ2M9kB6W5HJsf4gmXoGGz8SsfsQ==
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjw8Wgl1usvj9LCzF1c8PufEIG11V2PHCDNlYc66ccIiojQX79st1Lbp0BJXsa2bvZLYjfqyYP5gqkX7jLslmXPN+Vew91sRTmXJTlANlm/fChHg+Fq+lQK0IKGBIn9RlPDFH+NNoUIw4LbZ4etRJuOfMwiVKsOVOYuuLjiIJTkda9eS9zrhTRUXhUuMIxBLdeJEAYve6oBpcnTKpUbTV+DYlru3Yh6lSIevhA361s65oJauNHFQLQ7Ysi9apiF5hmqt1sThv/NPM/xLwlPrSGqKZWnclJbBKaWFlCijuM7W3Q5zbcdmtvKhxEJMobu+KMbt/LVhV7kD3BBLhADKnZ
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDzvqmjwxE3UgKOVZDoji9npAu7Uee47sdS60cTN0yx3Aj+c5IznoBLDYt7HwUcjKoj6soRJALFMGvrKe6n4H1+9jF5vrstMB40Ga8858wweehIAEzw/ONAORZdHA2y0WG8K3+bNOVSeXZwASsjbKrYcdotsZarhQtGVks6xQwd7qXUD44DDdFuWsuj5//hSSYSIgjJE3gAfeI2qVoe6Cl6gTGoK9zd+hNrYDehpN7bDgX45ulcaMw7N2kLf+Sg5QqOYL3Xdav/SeNEefNUyE058uRK8Br3WhZh5BJ2qFjzUYe20cFKHJ3gqKiY+8aor6YrDAS5AOEAEdCw1GWHJutGeApouTSqpNZf1uHspKEgLCUu6gb+i14k3YGSqUW/3fRdqmtN5qBYGvOoqgUEG1wlsxjf6lvJxSh6551MEiM3dpXBq3wniFjK56pj9nVjW0erJsOXPIqh9KeQL1dB+fzNX0r3oAog3EEl+x+V/YLE12b8MR9qnaZwyxWPGKoRE70=
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa 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==
Loading