Use JKS keystore for public key authentication with Ganymed SSH

I am trying to retrieve a private key from a java keystore and then pass it to Ganymed SSH to establish a public key authentication connection. However, he refuses to connect.

I can successfully connect via Cygwin ( ssh -i

) with the files generated by the following program, but the program itself cannot authenticate on the same computer. What am I doing wrong?

I don't get an exception, Connection.authenticateWithPublicKey (String, char [], String) just returns false, so it shouldn't be a formatting issue. It doesn't matter if I leave the private key unencrypted, the result is the same. Also, I was able to connect to a program like this with files generated by putty-gen and ssh-keygen.

To compile (bouncycastle and ganymed) you need the following:

Create a keystore with

keytool -genkeypair -keystore keystore.jks -alias myalias -storepass password -keypass password -keyalg RSA -dname CN=myalias,O=example.com -storetype JKS -validity 365 -v

      

Code (expects host and port as arguments and the aforementioned keystore in the working directory):

import ch.ethz.ssh2.Connection;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.openssl.PEMEncryptor;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;

public class KeystoreGanymedSSH {

    public static void main(String[] args)
            throws Exception {
        String keystorePath = "keystore.jks";
        char[] password = "password".toCharArray();
        String alias = "myalias";

        String host = args[0];
        int port = Integer.parseInt(args[1]);

        // keystore init
        KeyStore keystore = KeyStore.getInstance("JKS");
        InputStream in;
        try {
            in = new FileInputStream(keystorePath);
        } catch (FileNotFoundException ex) {
            System.out.println("Generate keystore using this command:");
            System.out.println("keytool -genkeypair -keystore keystore.jks"
                    + " -alias myalias -storepass password -keypass password"
                    + " -keyalg RSA -dname CN=myalias,O=example.com -storetype"
                    + " JKS -validity 365 -v");
            throw ex;
        }
        try {
            keystore.load(in, password);
        } finally {
            in.close();
        }

        // get public key in OpenSSH format
        String authorizedKeysEntry = genAuthorizedKeysEntry(keystore, alias);
        if (authorizedKeysEntry == null) {
            throw new Exception("could not generate authorized_keys entry");
        }
        System.out.println("Public key for pasting into OpenSSH authorized_keys file (always same):");
        System.out.println(authorizedKeysEntry);
        System.out.println();

        Writer writer;

        // write to file
        writer = new OutputStreamWriter(
                new FileOutputStream(new File("authorized_keys")), "UTF-8");
        try {
            writer.write(authorizedKeysEntry);
        } finally {
            writer.close();
        }

        // obtain PEM encrypted char[]
        Key key = keystore.getKey(alias, password);
        writer = new CharArrayWriter();
        JcaPEMWriter pw = new JcaPEMWriter(writer);
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        PEMEncryptor encryptor = new JcePEMEncryptorBuilder("DES-EDE3-CBC")
                .setSecureRandom(random).build(password);
        pw.writeObject(key, encryptor);
        pw.flush();
        char[] privateKey = ((CharArrayWriter)writer).toCharArray();

        System.out.println("Encrypted private key (changes on each run):");
        System.out.println(new String(privateKey));
        String name = "RSA".equals(key.getAlgorithm()) ? "id_rsa" : "id_dsa";
        writer = new OutputStreamWriter(
                new FileOutputStream(new File(name)), "UTF-8");
        try {
            writer.write(privateKey);
        } finally {
            writer.close();
        }

        // attempt ganymed connection   
        Connection connection = null;
        try {
            System.out.println("Connecting to " + host + ":" + port);
            connection = new Connection(host, port);        
            connection.connect(); // no known_hosts

            if (!connection.isAuthMethodAvailable(alias, "publickey")) {
                System.out.println("Public key auth is not available.");
                return;
            }

            boolean result = connection.authenticateWithPublicKey(
                    alias, privateKey, new String(password));

            System.out.println(result ? "Authentication success." : "Authentication failure.");
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

    }

    private static String genAuthorizedKeysEntry(
            KeyStore keystore, String alias) throws GeneralSecurityException, IOException {
        Certificate[] chain = keystore.getCertificateChain(alias);
        if (chain == null || chain.length <= 0) {
            return null;
        }
        PublicKey publicKey = chain[0].getPublicKey();
        if ("RSA".equals(publicKey.getAlgorithm())) {
            RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);
            String type = "ssh-rsa";
            dos.writeInt(type.getBytes("UTF-8").length);
            dos.write(type.getBytes("UTF-8"));
            byte[] exponent = rsaPublicKey.getPublicExponent().toByteArray();
            dos.writeInt(exponent.length);
            dos.write(exponent);
            byte[] modulus = rsaPublicKey.getModulus().toByteArray();
            dos.writeInt(modulus.length);
            dos.write(modulus);
            String encoded = DatatypeConverter.printBase64Binary(
                    baos.toByteArray());
            return type + " " + encoded + " " + alias;
        } else if ("DSA".equals(publicKey.getAlgorithm())) {
            DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
            DSAParams params = dsaPublicKey.getParams();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);
            String type = "ssh-dss";
            dos.writeInt(type.getBytes("UTF-8").length);
            dos.write(type.getBytes("UTF-8"));
            byte[] p = params.getP().toByteArray();
            dos.writeInt(p.length);
            dos.write(p);
            byte[] q = params.getQ().toByteArray();
            dos.writeInt(q.length);
            dos.write(q);
            byte[] g = params.getG().toByteArray();
            dos.writeInt(g.length);
            dos.write(g);
            byte[] y = dsaPublicKey.getY().toByteArray();
            dos.writeInt(y.length);
            dos.write(y);
            String encoded = DatatypeConverter.printBase64Binary(
                    baos.toByteArray());
            return type + " " + encoded + " " + alias;
        } else {
            return null;
        }
    }
}

      

Output example:

Public key for pasting into OpenSSH authorized_keys file (always same):
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCJLXgRaVdbZNuCsTgUsw2UPGdEA4La8ggQZWkevgAEMrgF+YYT2uN6BYDgD7hzs3ZTLXz2KUQLkMe7xLvimAsg6YXUi46IGEkTSOBFR0yYj+12O2BNbAxOXLIDIMBK5bsDwnuOsFedbeILFU4DaV+igJKO1zHWNbmbmd4RlfrIgH7Blfce8zSVkEdLkqEmydbg4xmj6r+MlzA5HSNZJILivb1XYNnoLjRH1SwUC8Rj6bjgBdNEXLOH0FNpCatHk9R00GaSZjcDZRKNAKnBSEIpw01TKaJlyQUTGqYGjK7UIbbafwMuYKR1rIzkyh4Usxvd3FvMdmKQSUeCnZU296YF myalias

Encrypted private key (changes on each run):
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,86DF7B50D4E319F6

QGngqwq+NXQGee7pEVROnwvIX6cyzo9QjdKtlherLCIYQjD4zagCvCd4vOUmwe0S
g/KaPeq8tanU0KB6m/WfAsgfOAkR+ujIod0hkUoCZp/Jg9LO2Me7ZFDNW/cBqbW0
CifaqDmOVgJ+HnZHJouMZyPf+To8SDdMSJZQ/3Wc2ZQn6BIhEjLzdz0hSVXGz2Gs
wmVYy4oedjB2f+dQliEnwFXBOusRMfqgiPqkdQj38voipqYBmPHUYSity0HGsRlo
KgovSeQhEDPT8GyYVJcKZLV3BUipvNvKJBP613wBZsuCtvhUNNNOfVFeTkD8+7PG
q2YzF3nasOM471EHyj18zPZ+DdPQMDPHtpVFQXJCFlf7xlGVAesPPUFJICXFE5OZ
JRAJC8+7WuT8O974uT9zDLeV5XLJRJK6o8dYXtZBK0YMpZy91b9axYDeVQh+Sfd+
V/T5RQ2osXg72lDBtz6vzeyMGj+Y9PQwZb97tdRW1X/ON4Eiiz/+1SVeDbWilo29
gMSPl4wb379Dvi7Z+b5OTGoF+F1p7Cp48sUEgIP8vPXinoOhBLdy1zq8oNVbo7PS
M5+41PKL4ao8pL9BCOALZpzP2R9LxoHjjL1auaWMzKLECtiEDvgu4GJeTVXvg7sO
yptecswVCF8fY+pV8dTtYU3vUs4UsdC9PG9IhqeRbML9dX7htsgkBmHdYAq5WOS/
RREuU+jyrCnc6kpOhIK/1wMOoIMOBnJ8EJXpMJaZtNwOQr05bOfFvozOEe35JwnP
NElP7CYBIvQrTyfrRxtJE+ntQO+uJbIvxFDY0EoQJX6YPFr0V7rnWy4W1yH0Yv6E
pmwERGYr1lbBIpxjTzTwZ3r845EUEwiwEt3+xfepBh3HUXg/mZYUw4cEz3HbzZDT
tWRPFpsBaicfatzbqvL7Teq1V8baUj1CW0wrANZbHc0FvSzuHMygub2ARgM3QAMj
L5yaITjH8/Tnbew7jPi5kSTXdNUnAJf3M/m6DC7svJtx+1Xwd0tfzp3GHYLaT+Mm
vOu8R5g/JJvBVMTzP8gyI32jDViRuHHwyFOlyJ35IrRCkW8i+aBmG1iT2WANWRai
2ujX4Gc+M2VncUdFR9MoCxUOy/7qKDcGNMpk8sgIi6Pc8SLiodueiWP3W6AXJKvs
u5akyk7jj8zq9+fe85T/cZ8lYe81hd3oA/9b/9cs8sdlhTmYjfUr1FgFHNyFPwdV
QnyayxeAy3xvoYXBBr7JrmWXLDTHghhMBHGHW7imoLNN8QZtTF+pGWzsxNcAVbEz
kmLll9ki0CUIbfufszp/b05OBC2M0EHn9uW61bwbiZfWxhfTlC2zHNHpig6zQhHu
q8n//KgHB5LDctGHoeqlUwoLbt78wd0bAD23GeZ2q1CdB6FYxoMYL8FuVOnxoUh3
fquXzH0wjv3Qm4Rwit+8zSdbD/+QbtJ2c/ZguUy4T3phI5BGzhLh2IDO8T9B6y5B
MmTyFjfZjVj+zU4F0BAIzzLlYTl332ecMj87StoNazqIF5Dj2ZqjUtF46MDeMZjO
tRvpIi8bWBm78rFNC51TZSBcfw714yOxHsPU0PqUMQMCgXawcDkTt2645/+ZZQtk
-----END RSA PRIVATE KEY-----

Connecting to 10.0.3.138:22
Authentication failure.

      

Edit01

I tried this with jsch and sshj and they all fail to connect. There must be something wrong in the above code to retrieve the private key. What puzzles me is what I can connect to ssh -i

. Also, if I upload the specified private key to putty-gen, I get the same authorized_keys line as in the output of my program (already present on my remote machine .ssh/authorized_keys

). But if I save it as a ppk file and then try to establish a putty session with it, it fails none (the server refused our key).

+3


source to share


1 answer


Noting a bug in the code I posted. It was just a huge setback on my part.

I used the keystore alias name as a username that did not exist as a user account in the remote Linux box. After creating an account, everything works. I was editing the wrong file authorized_keys

(on a different account) and expected it to work through some kind of nebulous dark magic known only to gnomes, unicorns and apparently my brain.

How does it work when I did ssh -i

... By default. Silly defaults. I didn't specify the username at all, so it defaulted to the one used by Cygwin, and it just so happened that it had the same name as the account on the remote machine where I was editing the file authorized_keys

.



Great. Awesome. Just..perfect. I need to find a bridge to throw myself out of here.

Doing what I am doing in my code works with ganymed, jsch and sshj (with some minor changes for the other two APIs).

+4


source







All Articles