From dbb84b4ec2d313ec0724d035c32f482ac57974c5 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 9 Jul 2022 10:30:32 +0200 Subject: [PATCH] Improve SSH layer --- .../src/org/argeo/cms/ssh/AbstractSsh.java | 73 ++++++++++++------- .../src/org/argeo/cms/ssh/SshKeyPair.java | 25 ++++++- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java index 2d195de9f..f2525bff8 100644 --- a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java +++ b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java @@ -20,7 +20,7 @@ import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider; import org.argeo.api.cms.CmsLog; @SuppressWarnings("restriction") -abstract class AbstractSsh { +public abstract class AbstractSsh { private final static CmsLog log = CmsLog.getLog(AbstractSsh.class); private static SshClient sshClient; @@ -31,7 +31,7 @@ abstract class AbstractSsh { private SshKeyPair sshKeyPair; - synchronized SshClient getSshClient() { + public synchronized SshClient getSshClient() { if (sshClient == null) { long begin = System.currentTimeMillis(); sshClient = SshClient.setUpDefaultClient(); @@ -51,33 +51,52 @@ abstract class AbstractSsh { return sftpFileSystemProvider; } - void authenticate() { - try { - if (sshKeyPair != null) { - session.addPublicKeyIdentity(sshKeyPair.asKeyPair()); - } else { - - if (!passwordSet) { - String password; - Console console = System.console(); - if (console == null) {// IDE - System.out.print("Password: "); - try (Scanner s = new Scanner(System.in)) { - password = s.next(); - } - } else { - console.printf("Password: "); - char[] pwd = console.readPassword(); - password = new String(pwd); - Arrays.fill(pwd, ' '); + public void authenticate() { + if (sshKeyPair != null) { + session.addPublicKeyIdentity(sshKeyPair.asKeyPair()); + } else { + + if (!passwordSet) { + String password; + Console console = System.console(); + if (console == null) {// IDE + System.out.print("Password: "); + try (Scanner s = new Scanner(System.in)) { + password = s.next(); } - session.addPasswordIdentity(password); - passwordSet = true; + } else { + console.printf("Password: "); + char[] pwd = console.readPassword(); + password = new String(pwd); + Arrays.fill(pwd, ' '); } + session.addPasswordIdentity(password); + passwordSet = true; } + } + verifyAuth(); + } + + public void verifyAuth() { + try { session.auth().verify(1000l); } catch (IOException e) { - throw new IllegalStateException(e); + throw new IllegalStateException("Cannot verify auth", e); + } + } + + public static char[] readPassword() { + Console console = System.console(); + if (console == null) {// IDE + System.out.print("Password: "); + try (Scanner s = new Scanner(System.in)) { + String password = s.next(); + return password.toCharArray(); + } + } else { + console.printf("Password: "); + char[] pwd = console.readPassword(); + return pwd; } } @@ -136,7 +155,7 @@ abstract class AbstractSsh { } } - void closeSession() { + public void closeSession() { if (session == null) throw new IllegalStateException("No session is open"); try { @@ -156,6 +175,10 @@ abstract class AbstractSsh { this.sshKeyPair = sshKeyPair; } + public static void openShell(AbstractSsh ssh) { + openShell(ssh.getSession()); + } + public static void openShell(ClientSession session) { try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) { channel.setIn(new NoCloseInputStream(System.in)); diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java index ed1818d40..f5cbb0450 100644 --- a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java +++ b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java @@ -20,9 +20,12 @@ import java.security.spec.RSAPublicKeySpec; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; @@ -125,12 +128,32 @@ public class SshKeyPair { } } + public static SshKeyPair loadDefault(char[] password) { + Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa"); + // TODO try other formats + return load(privateKeyPath, password); + } + + public static SshKeyPair load(Path privateKeyPath, char[] password) { + try (Reader reader = Files.newBufferedReader(privateKeyPath)) { + return load(reader, password); + } catch (IOException e) { + throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e); + } + + } + public static SshKeyPair load(Reader reader, char[] password) { try (PEMParser pemParser = new PEMParser(reader)) { Object object = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC"); KeyPair kp; - if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + if (object instanceof PEMEncryptedKeyPair) { + PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object; + PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password); + PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider); + kp = converter.getKeyPair(pemKp); + } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { // Encrypted key - we will use provided password PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object; // PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password); -- 2.39.2