androidsshkeystoreapache-minakey-pair

How can I use key from AndroidKeyStore instead of password to authenticate my SSH client connection?


I am unable to login to server using key authentication from my client Android app. I am able to login to server from my app using password authentication. I am able to login to server using key authentication from other clients on my network.

protected String sshConnect() {

    String stdoutString = null;
    String alias = "KEYPAIRTEST";

    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);
    KeyStore.Entry entry = keyStore.getEntry(alias, null);
    PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
    PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();
    KeyPair keyPair = new KeyPair(publicKey, privateKey);


    // create a client instance
    try (SshClient client = SshClient.setUpDefaultClient()) {
    
        client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE); // should be default
        client.addPublicKeyIdentity(keyPair);
        //client.addPasswordIdentity(this.password);
        client.start();

        // connection and authentication
        try (ClientSession session = client.connect(username, host, port).verify(TimeUnit.SECONDS.toMillis(defaultTimeoutSeconds)).getSession()) {

            session.auth().verify(TimeUnit.SECONDS.toMillis(defaultTimeoutSeconds));

            // create a channel to communicate
            ClientChannel channel = session.createExecChannel(this.command);

            ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
            channel.setOut(stdoutStream);

            // open channel
            channel.open().verify(5, TimeUnit.SECONDS);

            // close channel
            channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED),
                    TimeUnit.SECONDS.toMillis(5));

            // output after converting to string type
            stdoutString = stdoutStream.toString();

        } catch (Exception e) {
            Log.d(TAG, "sshConnect(): client.connect(): Exception: " + e);
        }
    } catch (Exception e) {
        Log.d(TAG, "sshConnect(): client.start(): Exception: " + e);
    }
    return stdoutString;
}

I got a string version of the public key with the following..

keyStore.getCertificate(alias).getPublicKey();
byte[] encodedPublicKey = publicKey.getEncoded();
String b64PublicKey = Base64.getEncoder().encodeToString(encodedPublicKey);

I pasted that public key on the server in ~/.ssh/authorized_keys.

ssh-rsa MlsdfijANBgkqhkiG9w0BAQE.. 

I also tried..

rsa-sha2-256 MlsdfijANBgkqhkiG9w0BAQE..

The keys were generated and stored stored on the client in the Android KeyStore using the following ..

KeyPairGenerator kpg = KeyPairGenerator.getInstance(
                        KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");

                kpg.initialize(new KeyGenParameterSpec.Builder(
                        alias,
                        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setKeySize(2048)
                        .build());

                kpg.generateKeyPair();

I tried to use the keys but login failed, the following warnings in my client logs..

W  60 [AsyncTask #1] INFO org.apache.sshd.common.io.DefaultIoServiceFactoryFactory - No detected/configured IoServiceFactoryFactory using Nio2ServiceFactoryFactory

WARN org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier - Server at /192.168.13.4:22 presented unverified EC key: SHA256:nNMe+ZsasdILlkGIZfcLwl41ZvVTzTaEOeA

and finally the exception..

Exception: org.apache.sshd.common.SshException: No more authentication methods available

What am I doing wrong?

Edit: I tried providing a KeyPairProvider with no luck..

try (SshClient client = SshClient.setUpDefaultClient()) {

    try {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        KeyStore.Entry entry = keyStore.getEntry(alias, null);
        PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
        PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();
        final KeyPair keyPair = new KeyPair(publicKey, privateKey);

        KeyPairProvider keyPairProvider = new KeyPairProvider() {
            @Override
            public Iterable<KeyPair> loadKeys(SessionContext session) {
                return Collections.singletonList(keyPair);
            }
        };

        client.setKeyIdentityProvider(keyPairProvider);

I also added the purposes encrypt and decrypt with no luck..

KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT

I was reading the docs for authorized_keys and the supported key types are..

sk-ecdsa-sha2-nistp256@openssh.com
ecdsa-sha2-nistp256
ecdsa-sha2-nistp384
ecdsa-sha2-nistp521
sk-ssh-ed25519@openssh.com
ssh-ed25519
ssh-dss
ssh-rsa

Can the "AndroidKeyStore" create any of these key types? What makes an RSA key an ssh-rsa key?


Solution

  • From tons of research, and through a heavy fog of ignorance, I started changing the structure of my RSA key generation to be more compatible to openSSH RSA.

    A huge step involved converting my RSA Public Key to the ssh-rsa format with the following..

    public static byte[] encode(RSAPublicKey key)
                    throws IOException
        {
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            byte[] name = "ssh-rsa".getBytes(StandardCharsets.US_ASCII);
            write(name, buf);
            write(key.getPublicExponent().toByteArray(), buf);
            write(key.getModulus().toByteArray(), buf);
            return buf.toByteArray();
        }
    
        private static void write(byte[] str, OutputStream os)
                throws IOException
        {
            for (int shift = 24; shift >= 0; shift -= 8)
                os.write((str.length >>> shift) & 0xFF);
            os.write(str);
        }
    
    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyStore.getCertificate(alias).getPublicKey();
    byte [] opensshPublicKey = encode(rsaPublicKey);
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        String b64PublicKey = Base64.getEncoder().encodeToString(opensshPublicKey);
    }
    

    Then I started getting exceptions like "INCOMPATIBLE_DIGEST" and "INCOMPATIBLE_PURPOSE".

    Then, through more research and trial and error, this led me to correct my RSA key generation to the following..

    KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    
    kpg.initialize(new KeyGenParameterSpec.Builder(alias,
    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
    .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
    .setUserAuthenticationRequired(false)
    .setKeySize(2048)
    .build());
    
    kpg.generateKeyPair();
    

    Then, miracle, it authenticated correctly.