From: Mathieu Baudier Date: Sun, 8 Sep 2019 15:36:55 +0000 (+0200) Subject: Improve SSH client. X-Git-Tag: argeo-commons-2.1.79~10 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=9ab1f7dea15511711b13dfef91b6aba74cbfc651 Improve SSH client. --- diff --git a/org.argeo.core/.gitignore b/org.argeo.core/.gitignore index 09e3bc9b2..c1639dd78 100644 --- a/org.argeo.core/.gitignore +++ b/org.argeo.core/.gitignore @@ -1,2 +1,4 @@ /bin/ /target/ +id_rsa +id_rsa.pub diff --git a/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java b/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java index 88b28b525..588c16b01 100644 --- a/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java +++ b/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java @@ -3,19 +3,15 @@ package org.argeo.ssh; import java.io.Console; import java.io.IOException; import java.net.URI; -import java.security.GeneralSecurityException; -import java.security.KeyPair; import java.util.Arrays; import java.util.Scanner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.sshd.client.SshClient; -import org.apache.sshd.client.config.keys.ClientIdentityLoader; import org.apache.sshd.client.future.ConnectFuture; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider; abstract class AbstractSsh { private final static Log log = LogFactory.getLog(AbstractSsh.class); @@ -48,6 +44,7 @@ abstract class AbstractSsh { return sftpFileSystemProvider; } + @SuppressWarnings("restriction") void authenticate() { try { if (sshKeyPair != null) { @@ -59,8 +56,9 @@ abstract class AbstractSsh { Console console = System.console(); if (console == null) {// IDE System.out.print("Password: "); - Scanner s = new Scanner(System.in); - password = s.next(); + try (Scanner s = new Scanner(System.in)) { + password = s.next(); + } } else { console.printf("Password: "); char[] pwd = console.readPassword(); @@ -99,6 +97,7 @@ abstract class AbstractSsh { openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null); } + @SuppressWarnings("restriction") void openSession(String login, String host, Integer port) { if (session != null) throw new IllegalStateException("Session is already open"); @@ -132,6 +131,7 @@ abstract class AbstractSsh { } } + @SuppressWarnings("restriction") void closeSession() { if (session == null) throw new IllegalStateException("No session is open"); diff --git a/org.argeo.core/src/org/argeo/ssh/SimpleSshServer.java b/org.argeo.core/src/org/argeo/ssh/SimpleSshServer.java index c1f90efe9..e63b989a4 100644 --- a/org.argeo.core/src/org/argeo/ssh/SimpleSshServer.java +++ b/org.argeo.core/src/org/argeo/ssh/SimpleSshServer.java @@ -11,6 +11,7 @@ import org.apache.sshd.server.shell.ProcessShellFactory; import org.argeo.util.os.OS; /** A simple SSH server with some defaults. Supports SCP. */ +@SuppressWarnings("restriction") public class SimpleSshServer { private Integer port; private Path hostKeyPath; diff --git a/org.argeo.core/src/org/argeo/ssh/Ssh.java b/org.argeo.core/src/org/argeo/ssh/Ssh.java index 584294cc0..044309877 100644 --- a/org.argeo.core/src/org/argeo/ssh/Ssh.java +++ b/org.argeo.core/src/org/argeo/ssh/Ssh.java @@ -19,6 +19,7 @@ import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.io.NoCloseInputStream; import org.apache.sshd.common.util.io.NoCloseOutputStream; +@SuppressWarnings("restriction") public class Ssh extends AbstractSsh { private final URI uri; @@ -83,6 +84,10 @@ public class Ssh extends AbstractSsh { } } + public URI getUri() { + return uri; + } + public static Options getOptions() { Options options = new Options(); // options.addOption("p", true, "port"); diff --git a/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java b/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java index 8feca58f9..f9b348598 100644 --- a/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java +++ b/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java @@ -1,8 +1,14 @@ package org.argeo.ssh; +import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; @@ -28,6 +34,8 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; @SuppressWarnings("restriction") public class SshKeyPair { + public final static String RSA_KEY_TYPE = "ssh-rsa"; + private PublicKey publicKey; private PrivateKey privateKey; private KeyPair keyPair; @@ -54,7 +62,7 @@ public class SshKeyPair { return PublicKeyEntry.toString(publicKey); } - public String getPrivateKeyAsString(char[] password) { + public String getPrivateKeyAsPemString(char[] password) { try { Object obj; @@ -79,9 +87,36 @@ public class SshKeyPair { } } + public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) { + try { + SshKeyPair sshKeyPair; + if (Files.exists(privateKeyPath)) { +// String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII); + sshKeyPair = load( + new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII), + password); + // TOD make sure public key is consistemt + } else { + sshKeyPair = generate(size); + Files.write(privateKeyPath, + sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII)); + Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub"); + Files.write(publicKeyPath, + sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII)); + } + return sshKeyPair; + } catch (IOException e) { + throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e); + } + } + public static SshKeyPair generate(int size) { + return generate(RSA_KEY_TYPE, size); + } + + public static SshKeyPair generate(String keyType, int size) { try { - KeyPair keyPair = KeyUtils.generateKeyPair("ssh-rsa", size); + KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); return new SshKeyPair(publicKey, privateKey); @@ -93,7 +128,7 @@ public class SshKeyPair { public static SshKeyPair load(Reader reader, char[] password) { try (PEMParser pemParser = new PEMParser(reader)) { Object object = pemParser.readObject(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC"); KeyPair kp; if (object instanceof PKCS8EncryptedPrivateKeyInfo) { // Encrypted key - we will use provided password @@ -125,22 +160,23 @@ public class SshKeyPair { } public static void main(String args[]) { - SshKeyPair okp = SshKeyPair.generate(1024); - System.out.println("Public:\n" + okp.getPublicKeyAsOpenSshString()); - System.out.println("Private (plain):\n" + okp.getPrivateKeyAsString(null)); - System.out.println("Private (encrypted):\n" + okp.getPrivateKeyAsString("demo".toCharArray())); - - StringReader reader = new StringReader(okp.getPrivateKeyAsString(null)); - okp = SshKeyPair.load(reader, null); - System.out.println("Public:\n" + okp.getPublicKeyAsOpenSshString()); - System.out.println("Private (plain):\n" + okp.getPrivateKeyAsString(null)); - System.out.println("Private (encrypted):\n" + okp.getPrivateKeyAsString("demo".toCharArray())); - - reader = new StringReader(okp.getPrivateKeyAsString("demo".toCharArray())); - okp = SshKeyPair.load(reader, "demo".toCharArray()); - System.out.println("Public:\n" + okp.getPublicKeyAsOpenSshString()); - System.out.println("Private (plain):\n" + okp.getPrivateKeyAsString(null)); - System.out.println("Private (encrypted):\n" + okp.getPrivateKeyAsString("demo".toCharArray())); + Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa"); + SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); + + StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null)); + skp = SshKeyPair.load(reader, null); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); + + reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray())); + skp = SshKeyPair.load(reader, "demo".toCharArray()); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); } } diff --git a/org.argeo.core/src/org/argeo/sync/fs/SshSync.java b/org.argeo.core/src/org/argeo/sync/fs/SshSync.java index 7fe6670c9..9b15a32ea 100644 --- a/org.argeo.core/src/org/argeo/sync/fs/SshSync.java +++ b/org.argeo.core/src/org/argeo/sync/fs/SshSync.java @@ -6,13 +6,8 @@ import java.io.OutputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.security.KeyPair; -import java.security.PublicKey; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Scanner; -import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -22,16 +17,10 @@ import org.apache.sshd.agent.SshAgentFactory; import org.apache.sshd.agent.local.LocalAgentFactory; import org.apache.sshd.agent.unix.UnixAgentFactory; import org.apache.sshd.client.SshClient; -import org.apache.sshd.client.channel.ClientChannel; -import org.apache.sshd.client.channel.ClientChannelEvent; -import org.apache.sshd.client.config.keys.ClientIdentityLoader; import org.apache.sshd.client.future.ConnectFuture; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem; import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.util.io.NoCloseInputStream; -import org.apache.sshd.common.util.io.NoCloseOutputStream; public class SshSync { private final static Log log = LogFactory.getLog(SshSync.class);