In an Android app, I am attempting to connect to an SSH server using the JSch library. The remote server is specified by the user, so I don't know the remote fingerprint in advance. At the same time I don't want to set StrictHostKeyChecking
to no as I see in so many examples.
I'd like to get the remote server fingerprint, show it to the user for acceptance. Is this possible either with JSch or regular Java, perhaps with sockets?
Here's an example you can try, just paste it in the onCreate
of an Android activity:
new Thread(new Runnable() {
@Override
public void run() {
com.jcraft.jsch.Session session;
JSch jsch;
try {
jsch = new JSch();
jsch.setLogger(new MyLogger());
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
//**Get a host key and show it to the user**
session.connect(); // reject HostKey: github.com
}
catch (Exception e){
LOG.error("Could not JSCH", e);
}
}
}).start();
OK I've found a way to do this. It may not be the best way but it is a way. Using the UserInfo.promptYesNo
required looping at the expense of CPU while waiting for user response or with the overhead of an Executor/FutureTask/BlockingQueue. Instead the async thread which executes the connection (since network tasks cannot occur on UI thread) is more conducive to doing this twice - once to 'break' and get the user to accept, second to succeed. I guess this is the 'Android way'. For this, the hostkey needs storing somewhere. Suppose I store it in Android's PreferenceManager, then to start with grab the key from there, defaulting to empty if not available
String keystring = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("target_hostkey","");
if(!Strings.isNullOrEmpty(keystring)){
byte[] key = Base64.decode ( keystring, Base64.DEFAULT );
jsch.getHostKeyRepository().add(new HostKey("github.com", key ), null);
}
Next, proceed as usual to connect to the server
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
session.connect();
But this time, catch the JSchException
. In there, the session has a HostKey available.
catch(final JSchException jex){
LOG.debug(session.getHostKey().getKey());
final com.jcraft.jsch.Session finalSession = session;
runOnUiThread(new Runnable() {
@Override
public void run() {
new MaterialDialog.Builder(MyActivity.this)
.title("Accept this host with fingerprint?")
.negativeText(R.string.cancel)
.positiveText(R.string.ok)
.content(finalSession.getHostKey().getFingerPrint(jsch))
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit().putString("target_hostkey", finalSession.getHostKey().getKey()).apply();
}
}).show();
}
});
}
After this, it's a matter of re-invoking the Thread or AsyncTask but this time the hostkey is added to the hostkey repository for JSch.