From 2604d709c4554fecdf54a7d4e03cfb4d3c29d987 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 8 Sep 2019 15:24:33 +0200 Subject: [PATCH] SSH key pair management. --- .../src/org/argeo/ssh/AbstractSsh.java | 35 +++-- .../src/org/argeo/ssh/SshKeyPair.java | 146 ++++++++++++++++++ 2 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 org.argeo.core/src/org/argeo/ssh/SshKeyPair.java diff --git a/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java b/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java index ae1d6a0d4..88b28b525 100644 --- a/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java +++ b/org.argeo.core/src/org/argeo/ssh/AbstractSsh.java @@ -26,6 +26,8 @@ abstract class AbstractSsh { private boolean passwordSet = false; private ClientSession session; + private SshKeyPair sshKeyPair; + synchronized SshClient getSshClient() { if (sshClient == null) { long begin = System.currentTimeMillis(); @@ -48,21 +50,26 @@ abstract class AbstractSsh { void authenticate() { try { - if (!passwordSet) { - String password; - Console console = System.console(); - if (console == null) {// IDE - System.out.print("Password: "); - Scanner s = new Scanner(System.in); - password = s.next(); - } else { - console.printf("Password: "); - char[] pwd = console.readPassword(); - password = new String(pwd); - Arrays.fill(pwd, ' '); + if (sshKeyPair != null) { + session.addPublicKeyIdentity(sshKeyPair.asKeyPair()); + } else { + + if (!passwordSet) { + String password; + Console console = System.console(); + if (console == null) {// IDE + System.out.print("Password: "); + Scanner s = new Scanner(System.in); + password = s.next(); + } else { + console.printf("Password: "); + char[] pwd = console.readPassword(); + password = new String(pwd); + Arrays.fill(pwd, ' '); + } + session.addPasswordIdentity(password); + passwordSet = true; } - session.addPasswordIdentity(password); - passwordSet = true; } session.auth().verify(1000l); } catch (IOException e) { diff --git a/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java b/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java new file mode 100644 index 000000000..8feca58f9 --- /dev/null +++ b/org.argeo.core/src/org/argeo/ssh/SshKeyPair.java @@ -0,0 +1,146 @@ +package org.argeo.ssh; + +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +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.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; + +@SuppressWarnings("restriction") +public class SshKeyPair { + private PublicKey publicKey; + private PrivateKey privateKey; + private KeyPair keyPair; + + public SshKeyPair(KeyPair keyPair) { + super(); + this.publicKey = keyPair.getPublic(); + this.privateKey = keyPair.getPrivate(); + this.keyPair = keyPair; + } + + public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) { + super(); + this.publicKey = publicKey; + this.privateKey = privateKey; + this.keyPair = new KeyPair(publicKey, privateKey); + } + + public KeyPair asKeyPair() { + return keyPair; + } + + public String getPublicKeyAsOpenSshString() { + return PublicKeyEntry.toString(publicKey); + } + + public String getPrivateKeyAsString(char[] password) { + try { + Object obj; + + if (password != null) { + JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder( + PKCS8Generator.PBE_SHA1_3DES); + encryptorBuilder.setPasssword(password); + OutputEncryptor oe = encryptorBuilder.build(); + JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe); + obj = gen.generate(); + } else { + obj = privateKey; + } + + StringWriter sw = new StringWriter(); + JcaPEMWriter pemWrt = new JcaPEMWriter(sw); + pemWrt.writeObject(obj); + pemWrt.close(); + return sw.toString(); + } catch (Exception e) { + throw new RuntimeException("Cannot convert private key", e); + } + } + + public static SshKeyPair generate(int size) { + try { + KeyPair keyPair = KeyUtils.generateKeyPair("ssh-rsa", size); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + return new SshKeyPair(publicKey, privateKey); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Cannot generate SSH key", 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) { + // Encrypted key - we will use provided password + PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object; +// PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password); + InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder() + .build(password); + PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider); + PrivateKey privateKey = converter.getPrivateKey(pkInfo); + + // generate public key + RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey; + RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(), + privk.getPublicExponent()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + kp = new KeyPair(publicKey, privateKey); + } else { + // Unencrypted key - no password needed +// PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object; + PEMKeyPair pemKp = (PEMKeyPair) object; + kp = converter.getKeyPair(pemKp); + } + return new SshKeyPair(kp); + } catch (Exception e) { + throw new RuntimeException("Cannot load private key", e); + } + } + + 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())); + } + +} -- 2.30.2