diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java index 2ee12793cdfe3a5545f8242e65201dc8bd30098d..f24549f8c5f06b4303ab32cd034f70819cef03ce 100644 --- a/cli/src/main/java/hudson/cli/CLI.java +++ b/cli/src/main/java/hudson/cli/CLI.java @@ -23,7 +23,6 @@ */ package hudson.cli; -import com.trilead.ssh2.crypto.PEMDecoder; import hudson.cli.client.Messages; import hudson.remoting.Channel; import hudson.remoting.PingThread; @@ -49,7 +48,6 @@ import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -61,13 +59,10 @@ import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.security.GeneralSecurityException; -import java.security.KeyFactory; import java.security.KeyPair; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -78,8 +73,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; -import java.io.Console; - import static java.util.logging.Level.*; /** @@ -391,7 +384,7 @@ public class CLI { public static int _main(String[] _args) throws Exception { List args = Arrays.asList(_args); - List candidateKeys = new ArrayList(); + PrivateKeyProvider provider = new PrivateKeyProvider(); boolean sshAuthRequestedExplicitly = false; String httpProxy=null; @@ -431,17 +424,9 @@ public class CLI { printUsage(Messages.CLI_NoSuchFileExists(f)); return -1; } - KeyPair kp; - try { - kp = loadKey(f); - } catch (IOException e) { - //if the PEM file is encrypted, IOException is thrown - kp = tryEncryptedFile(f); - } catch (GeneralSecurityException e) { - throw new Exception("Failed to load key: "+f,e); - } - if(kp != null) - candidateKeys.add(kp); + + provider.readFrom(f); + args = args.subList(2,args.size()); sshAuthRequestedExplicitly = true; continue; @@ -462,8 +447,8 @@ public class CLI { if(args.isEmpty()) args = Arrays.asList("help"); // default to help - if (candidateKeys.isEmpty()) - addDefaultPrivateKeyLocations(candidateKeys); + if (!provider.hasKeys()) + provider.readFromDefaultLocations(); CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy); String userInfo = new URL(url).getUserInfo(); @@ -473,10 +458,10 @@ public class CLI { CLI cli = factory.connect(); try { - if (!candidateKeys.isEmpty()) { + if (provider.hasKeys()) { try { // TODO: server verification - cli.authenticate(candidateKeys); + cli.authenticate(provider.getKeys()); } catch (IllegalStateException e) { if (sshAuthRequestedExplicitly) { System.err.println("The server doesn't support public key authentication"); @@ -528,97 +513,22 @@ public class CLI { * Loads RSA/DSA private key in a PEM format into {@link KeyPair}. */ public static KeyPair loadKey(File f, String passwd) throws IOException, GeneralSecurityException { - return loadKey(readPemFile(f), passwd); + return PrivateKeyProvider.loadKey(f, passwd); } public static KeyPair loadKey(File f) throws IOException, GeneralSecurityException { - return loadKey(f, null); - } - - private static String readPemFile(File f) throws IOException{ - FileInputStream is = new FileInputStream(f); - try { - DataInputStream dis = new DataInputStream(is); - byte[] bytes = new byte[(int) f.length()]; - dis.readFully(bytes); - dis.close(); - return new String(bytes); - } finally { - is.close(); - } + return loadKey(f, null); } - + /** * Loads RSA/DSA private key in a PEM format into {@link KeyPair}. */ public static KeyPair loadKey(String pemString, String passwd) throws IOException, GeneralSecurityException { - Object key = PEMDecoder.decode(pemString.toCharArray(), passwd); - if (key instanceof com.trilead.ssh2.signature.RSAPrivateKey) { - com.trilead.ssh2.signature.RSAPrivateKey x = (com.trilead.ssh2.signature.RSAPrivateKey)key; -// System.out.println("ssh-rsa " + new String(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey(x.getPublicKey())))); - - return x.toJCEKeyPair(); - } - if (key instanceof com.trilead.ssh2.signature.DSAPrivateKey) { - com.trilead.ssh2.signature.DSAPrivateKey x = (com.trilead.ssh2.signature.DSAPrivateKey)key; - KeyFactory kf = KeyFactory.getInstance("DSA"); -// System.out.println("ssh-dsa " + new String(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey(x.getPublicKey())))); - - return new KeyPair( - kf.generatePublic(new DSAPublicKeySpec(x.getY(), x.getP(), x.getQ(), x.getG())), - kf.generatePrivate(new DSAPrivateKeySpec(x.getX(), x.getP(), x.getQ(), x.getG()))); - } - - throw new UnsupportedOperationException("Unrecognizable key format: "+key); + return PrivateKeyProvider.loadKey(pemString, passwd); } public static KeyPair loadKey(String pemString) throws IOException, GeneralSecurityException { - return loadKey(pemString, null); - } - - private static KeyPair tryEncryptedFile(File f) throws IOException, GeneralSecurityException{ - KeyPair kp = null; - if(isPemEncrypted(f)){ - String passwd = askForPasswd(f.getCanonicalPath()); - kp = loadKey(f,passwd); - } - return kp; - } - - private static boolean isPemEncrypted(File f) throws IOException{ - String pemString = readPemFile(f); - //simple check if the file is encrypted - return pemString.contains("4,ENCRYPTED"); - } - - private static String askForPasswd(String filePath){ - Console cons = System.console(); - String passwd = null; - if (cons != null){ - char[] p = cons.readPassword("%s", "Enter passphrase for "+filePath+":"); - passwd = String.valueOf(p); - } - return passwd; - } - - /** - * try all the default key locations - */ - private static void addDefaultPrivateKeyLocations(List keyFileCandidates) { - File home = new File(System.getProperty("user.home")); - for (String path : new String[]{".ssh/id_rsa",".ssh/id_dsa",".ssh/identity"}) { - File key = new File(home,path); - if (key.exists()) { - try { - keyFileCandidates.add(loadKey(key)); - } catch (IOException e) { - // don't report an error. the user can still see it by using the -i option - LOGGER.log(FINE, "Failed to load "+key,e); - } catch (GeneralSecurityException e) { - LOGGER.log(FINE, "Failed to load " + key, e); - } - } - } + return loadKey(pemString, null); } /** diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..834bb7234250d7d7bac2bd424aba5aa670be0fda --- /dev/null +++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java @@ -0,0 +1,162 @@ +/* + * The MIT License + * + * Copyright (c) 2014 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.cli; + +import static java.util.logging.Level.FINE; + +import java.io.Console; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import com.trilead.ssh2.crypto.PEMDecoder; + +/** + * Read DSA or RSA key from file(s) asking for password interactively. + * + * @author ogondza + * @since 1.556 + */ +public class PrivateKeyProvider { + + private List privateKeys = new ArrayList(); + + /** + * Get keys read so far. + * + * @return Possibly empty list. Never null. + */ + public List getKeys() { + return Collections.unmodifiableList(privateKeys); + } + + public boolean hasKeys() { + return !privateKeys.isEmpty(); + } + + /** + * Read keys from default keyFiles + * + * .ssh/id_rsa, .ssh/id_dsa and .ssh/identity. + * + * @return true if some key was read successfully. + */ + public boolean readFromDefaultLocations() { + final File home = new File(System.getProperty("user.home")); + + boolean read = false; + for (String path : new String[] {".ssh/id_rsa", ".ssh/id_dsa", ".ssh/identity"}) { + final File key = new File(home, path); + if (!key.exists()) continue; + + try { + + readFrom(key); + read = true; + } catch (IOException e) { + + LOGGER.log(FINE, "Failed to load " + key, e); + } catch (GeneralSecurityException e) { + + LOGGER.log(FINE, "Failed to load " + key, e); + } + } + + return read; + } + + /** + * Read key from keyFile. + */ + public void readFrom(File keyFile) throws IOException, GeneralSecurityException { + final String password = isPemEncrypted(keyFile) + ? askForPasswd(keyFile.getCanonicalPath()) + : null + ; + privateKeys.add(loadKey(keyFile, password)); + } + + private static boolean isPemEncrypted(File f) throws IOException{ + //simple check if the file is encrypted + return readPemFile(f).contains("4,ENCRYPTED"); + } + + private static String askForPasswd(String filePath){ + Console cons = System.console(); + String passwd = null; + if (cons != null){ + char[] p = cons.readPassword("%s", "Enter passphrase for " + filePath + ":"); + passwd = String.valueOf(p); + } + return passwd; + } + + public static KeyPair loadKey(File f, String passwd) throws IOException, GeneralSecurityException { + return loadKey(readPemFile(f), passwd); + } + + private static String readPemFile(File f) throws IOException{ + FileInputStream is = new FileInputStream(f); + try { + DataInputStream dis = new DataInputStream(is); + byte[] bytes = new byte[(int) f.length()]; + dis.readFully(bytes); + dis.close(); + return new String(bytes); + } finally { + is.close(); + } + } + + public static KeyPair loadKey(String pemString, String passwd) throws IOException, GeneralSecurityException { + Object key = PEMDecoder.decode(pemString.toCharArray(), passwd); + if (key instanceof com.trilead.ssh2.signature.RSAPrivateKey) { + com.trilead.ssh2.signature.RSAPrivateKey x = (com.trilead.ssh2.signature.RSAPrivateKey)key; + + return x.toJCEKeyPair(); + } + if (key instanceof com.trilead.ssh2.signature.DSAPrivateKey) { + com.trilead.ssh2.signature.DSAPrivateKey x = (com.trilead.ssh2.signature.DSAPrivateKey)key; + KeyFactory kf = KeyFactory.getInstance("DSA"); + + return new KeyPair( + kf.generatePublic(new DSAPublicKeySpec(x.getY(), x.getP(), x.getQ(), x.getG())), + kf.generatePrivate(new DSAPrivateKeySpec(x.getX(), x.getP(), x.getQ(), x.getG()))); + } + + throw new UnsupportedOperationException("Unrecognizable key format: " + key); + } + + private static final Logger LOGGER = Logger.getLogger(PrivateKeyProvider.class.getName()); +} diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..376b1fa815f3f62988f741c93a63f2f2d090563a --- /dev/null +++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java @@ -0,0 +1,140 @@ +/* + * The MIT License + * + * Copyright (c) 2014 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.cli; + +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.doReturn; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.whenNew; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.util.Arrays; + +import org.hamcrest.Description; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(CLI.class) // When mocking new operator caller has to be @PreparedForTest, not class itself +public class PrivateKeyProviderTest { + + @Test + public void specifyKeysExplicitly() throws Exception { + final CLI cli = fakeCLI(); + + final File dsaKey = keyFile(".ssh/id_dsa"); + final File rsaKey = keyFile(".ssh/id_rsa"); + + run("-i", dsaKey.getAbsolutePath(), "-i", rsaKey.getAbsolutePath(), "-s", "http://example.com"); + + verify(cli).authenticate(withKeyPairs( + keyPair(dsaKey), + keyPair(rsaKey) + )); + } + + @Test + public void useDefaultKeyLocations() throws Exception { + final CLI cli = fakeCLI(); + + final File rsaKey = keyFile(".ssh/id_rsa"); + final File dsaKey = keyFile(".ssh/id_dsa"); + + fakeHome(); + run("-s", "http://example.com"); + + verify(cli).authenticate(withKeyPairs( + keyPair(rsaKey), + keyPair(dsaKey) + )); + } + + private CLI fakeCLI() throws Exception { + final CLI cli = mock(CLI.class); + + final CLIConnectionFactory factory = mock(CLIConnectionFactory.class, Mockito.CALLS_REAL_METHODS); + factory.jenkins = new URL("http://example.com"); + doReturn(cli).when(factory).connect(); + + mockStatic(CLIConnectionFactory.class); + whenNew(CLIConnectionFactory.class).withNoArguments().thenReturn(factory); + + return cli; + } + + private void fakeHome() throws URISyntaxException { + final File home = new File(this.getClass().getResource(".ssh").toURI()).getParentFile(); + System.setProperty("user.home", home.getAbsolutePath()); + } + + private int run(String... args) throws Exception { + return CLI._main(args); + } + + private File keyFile(String name) throws URISyntaxException { + return new File(this.getClass().getResource(name).toURI()); + } + + private KeyPair keyPair(File file) throws IOException, GeneralSecurityException { + return PrivateKeyProvider.loadKey(file, null); + } + + private Iterable withKeyPairs(final KeyPair... expected) { + return Mockito.argThat(new ArgumentMatcher>() { + @Override public void describeTo(Description description) { + description.appendText(Arrays.asList(expected).toString()); + } + + @Override public boolean matches(Object argument) { + if (!(argument instanceof Iterable)) throw new IllegalArgumentException("Not an instance of Iterrable"); + + @SuppressWarnings("unchecked") + final Iterable actual = (Iterable) argument; + int i = 0; + for (KeyPair akp: actual) { + if (!eq(expected[i].getPublic(), akp.getPublic())) return false; + if (!eq(expected[i].getPrivate(), akp.getPrivate())) return false; + i++; + } + + return i == expected.length; + } + + private boolean eq(final Key expected, final Key actual) { + return Arrays.equals(expected.getEncoded(), actual.getEncoded()); + } + }); + } +} diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa b/cli/src/test/resources/hudson/cli/.ssh/id_dsa new file mode 100644 index 0000000000000000000000000000000000000000..be556daa6d02984101686a02939b6777c780db86 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/.ssh/id_dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCA9mMzB1O52hpObIyaJXgFJQUmc1HV0NEJXsFFGh8U2l0Tkgv4 +fp3MWadiAMmc5H1ot4KQLXl7SwU7dHCCFcGcfQiOjeD5rWeZuHoPAJSDMilcJGE3 +Xo2C+wlescTByEgRRA16vdSlNaDJXKVxq9Wr59G8P4JC6/5EvpeypgYdTQIVAMTf +aC0O2EGLnJrNBsUdc1s+iUp9AoGAZA7pZYPMJHJWTanJb2DlWHn/QM63jfh38N6W +ERzmQQks6QdS7UkFlg9cbVGUtn0Yz2SfX3VKiMXNMkAdGD8loBcJS5w6oMMU7rcj +lldRQ63+fMgdVZYMF5bchC6RhQeGZQ8Imf2iFF28SsE4bi+K12HYgIO5bFxPFUTH +WSWsMLcCgYBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc +2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYx +uWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHwIUUkttGGgl +a0rWLzPTPF4X4lZfFhk= +-----END DSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub new file mode 100644 index 0000000000000000000000000000000000000000..4a42a845727297abcfa1c1204d0a270392c5a9a6 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAID2YzMHU7naGk5sjJoleAUlBSZzUdXQ0QlewUUaHxTaXROSC/h+ncxZp2IAyZzkfWi3gpAteXtLBTt0cIIVwZx9CI6N4PmtZ5m4eg8AlIMyKVwkYTdejYL7CV6xxMHISBFEDXq91KU1oMlcpXGr1avn0bw/gkLr/kS+l7KmBh1NAAAAFQDE32gtDthBi5yazQbFHXNbPolKfQAAAIBkDullg8wkclZNqclvYOVYef9AzreN+Hfw3pYRHOZBCSzpB1LtSQWWD1xtUZS2fRjPZJ9fdUqIxc0yQB0YPyWgFwlLnDqgwxTutyOWV1FDrf58yB1VlgwXltyELpGFB4ZlDwiZ/aIUXbxKwThuL4rXYdiAg7lsXE8VRMdZJawwtwAAAIBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYxuWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHw== ogondza@localhost.localdomain diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa b/cli/src/test/resources/hudson/cli/.ssh/id_rsa new file mode 100644 index 0000000000000000000000000000000000000000..ee2ad6b569a3b5ebbea0d5d1bfcf6ed2bd0dbe27 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/.ssh/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyTqwFqp5Ww2Tr/52D7hhdOwgzYGBUqxrOFopa+kjNEL1Yqwb ++mApUWZ+D3zN9PurhUcVUfeYVXiYWFJ0kG72HIJawL/0BR5oYxRJfumK8Z/sAzAL +xdhc5O5twETrr9gU3cxtvF5oJNP0I9HickAOeC+ZNpiDIIblrhvxXl/QwqrR+/Gv +Nb8TApj+rxXEfNp+N69iGnnxzWn1FeKeOAWpwoBAxZNoqBQAFacF7xfQnoygyekC +xk+ts2O5Zzv8iJ10sVf+x2Q79rxAtsc0xOGhZbBAzbmFTz0PE4iWuo/Vo1c6mM7u +/dam+FxB2NqPNw7W+4eiCnEVkiQZlrxmuGvK7wIDAQABAoIBACml1+QZDFzoBnUa +eVzvkFwesvtVnmp5/QcAwinvarXaVedCL9g2JtcOG3EhJ49YtzsyZxs7329xMja1 +eiKalJ157UaPc/XLQVegT0XRGEzCCJrwSr979F39awGsQgt28XqmYN/nui5FH/Z5 +7iAvWc9OKqu+DQWiZc8PQXmC4zYmvhGQ8vKx44RSqlWCjd9IqBVhpE5gxpI/SmCx +umUNNtoH0hBWr+MsVHzr6UUrC3a99+7bB4We8XMXXFLzbTUSgiYFmK+NxPs/Fux/ +IAyXAMbDw2HeqZ7g4kTaf4cvmVOwhh4zlvB4p7j301LdO1jmvs9z0fn/QJcTpVM7 +ISMKwAECgYEA/uKVdmOKTk3dKzKRFXtWJjqypOXakoX+25lUcVv2PXYRr8Sln9jC +A13fbhvwq+FqbdnNlB23ag5niCVLfUpB1DYYP5jd4lU8D6HZQiHlmokB6nLT9NIW +iTcG88E58Bta/l1Ue5Yn+LqluBC4i289wFbH1kZyxQ565s5dJEv9uAECgYEAyhwF +ZOqTK2lZe5uuN4owVLQaYFj9fsdFHULzlK/UAtkG1gCJhjBmwSEpZFFMH6WgwHk5 +SHJEom0uB4qRv8gQcxl9OSiDsp56ymr0NBhlPVXWr6IzLotLy5XBC1muqvYYlj7E +kHgSet/h8RUM/FeEiwOFHDU2DkMb8Qx1hfMdAu8CgYBSEsYL9CuB4WK5WTQMlcV8 +0+PYY0dJbSpOrgXZ5sHYsp8pWQn3+cUnbl/WxdpujkxGCR9AdX0tAmxmE5RGSNX/ +rleKiv/PtKB9bCFYQS/83ecnBkioCcpF7tknPm4YmcZoJ8dfcE94sSlRpti11WEu +AQOiRNcKCwqaLZMib/HIAQKBgQCdiOffeERMYypfgcJzAiCX9WZV0SeOCS7jFwub +ys17hsSgS/zl/pYpVXrY+dFXHZfGTvcKdB7xaB6nvCfND9lajfSgd+bndEYLvwAo +Fxfajizv64LvdZ4XytuUyEuwcHBLtBMs9Jqa8iU/8AOWMXVbkdvQV92RkleWNPrp +9MyZOwKBgQD9x8MnX5LVBfQKuL9qX6l9Da06EyMkzfz3obKn9AAJ3Xj9+45TNPJu +HnZyvJWesl1vDjXQTm+PVkdyE0WQgoiVX+wxno0hsoly5Uqb5EYHtTUrZzRpkyLK +1VmtDxT5D8gorUgn6crzk4PKaxRkPfAimZdlkQm6iOtuR3kqn5BtIQ== +-----END RSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub new file mode 100644 index 0000000000000000000000000000000000000000..91f8ff7180e5f05f989b9aec2104d102372e88a4 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJOrAWqnlbDZOv/nYPuGF07CDNgYFSrGs4Wilr6SM0QvVirBv6YClRZn4PfM30+6uFRxVR95hVeJhYUnSQbvYcglrAv/QFHmhjFEl+6Yrxn+wDMAvF2Fzk7m3AROuv2BTdzG28Xmgk0/Qj0eJyQA54L5k2mIMghuWuG/FeX9DCqtH78a81vxMCmP6vFcR82n43r2IaefHNafUV4p44BanCgEDFk2ioFAAVpwXvF9CejKDJ6QLGT62zY7lnO/yInXSxV/7HZDv2vEC2xzTE4aFlsEDNuYVPPQ8TiJa6j9WjVzqYzu791qb4XEHY2o83Dtb7h6IKcRWSJBmWvGa4a8rv your_email@example.com