The official Mina SSHD server example is oversimplified, so I'd like to work out a more useful example.
PART 1: Add Password Authentication
show source directly:
import java.io.File; import java.io.IOException; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.auth.password.PasswordAuthenticator; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.shell.ProcessShellFactory; public class SshServerMock { public static void main(String[] args) throws IOException { SshServer sshd = SshServer.setUpDefaultServer(); sshd.setPort(22); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("d:\\hostkey.ser"))); sshd.setPasswordAuthenticator(new PasswordAuthenticator() { @Override public boolean authenticate(String u, String p, ServerSession s) { return ("sshtest".equals(u) && "sshtest".equals(p)); } }); sshd.setShellFactory(new ProcessShellFactory(new String[] { "cmd.exe" })); sshd.start(); try { Thread.sleep(100000000000l); } catch (InterruptedException e) { e.printStackTrace(); } } }
PART 2: Public Key Authentication
At first we need to know the public key authenticating process, quoted from [3]:
What really happens is as follows: (1) The server asks for the public key of the client. (2) The public key is passed to PublickeyAuthenticator#authenticate (3) All you are supposed to do in authenticate() is check that this is a public key that you want to allow. (4) If authenticate() returns true, then UserAuthPublicKey#auth will check that a message has been signed with the private key. If it has then authentication has been validated.
Second, we must be aware of the two forms of public key, one is OpenSSH generated public key, second is the java public key in the Mina SSHD API. Therefore, when coding for SSHD server, you will get client's public key in java form (java.security.interfaces.RSAPublicKey), but the server's persisted public key is in OpenSSH form, so we need to transform before validating.
/* * http://caffeineiscodefuel.blogspot.hk/2013/04/apache-mina-sshd- * publickeyauthenticator.html */ class CustomPublicKeyAuthenticator implements PublickeyAuthenticator { private static final String knownKey = "{SSH2.PUBLIC.KEY}"; @Override public boolean authenticate(String username, PublicKey key, ServerSession session) { if (key instanceof RSAPublicKey) { String s1 = new String(encode((RSAPublicKey) key)); String s2 = new String(Base64.decodeBase64(knownKey.getBytes())); return s1.equals(s2); // Returns true if the key matches our known // key, this allows auth to proceed. } return false; // Doesn't handle other key types currently. } // Converts a Java RSA PK to SSH2 Format. public static byte[] encode(RSAPublicKey key) { try { ByteArrayOutputStream buf = new ByteArrayOutputStream(); byte[] name = "ssh-rsa".getBytes("US-ASCII"); write(name, buf); write(key.getPublicExponent().toByteArray(), buf); write(key.getModulus().toByteArray(), buf); return buf.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } 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); } }
PART 3 Expose Java Service to SSH by Customized ShellFactory
Once you plug in your own authentication logic and custom shell, the sky’s the limit.
The best example for this I found resides in the Mina SSHD unit test source code, the class named 'EchoShellFactory'
/** * @author Apache MINA SSHD Project */ public class EchoShellFactory implements Factory<Command> { public Command create() { return new EchoShell(); } public static class EchoShell implements Command, Runnable { private InputStream in; private OutputStream out; private OutputStream err; private ExitCallback callback; private Environment environment; private Thread thread; public InputStream getIn() { return in; } public OutputStream getOut() { return out; } public OutputStream getErr() { return err; } public Environment getEnvironment() { return environment; } public void setInputStream(InputStream in) { this.in = in; } public void setOutputStream(OutputStream out) { this.out = out; } public void setErrorStream(OutputStream err) { this.err = err; } public void setExitCallback(ExitCallback callback) { this.callback = callback; } public void start(Environment env) throws IOException { environment = env; thread = new Thread(this, "EchoShell"); thread.start(); } public void destroy() { thread.interrupt(); } public void run() { BufferedReader r = new BufferedReader(new InputStreamReader(in)); try { for (;;) { String s = r.readLine(); if (s == null) { return; } out.write((s + "\r\n").getBytes()); //note the '\r' out.flush(); if ("exit".equals(s)) { return; } } } catch (Exception e) { e.printStackTrace(); } finally { callback.onExit(0); } } } }Note that without the '\r', the new line will not start from the beginning, it looks weird. so we'd better add this carriage return character.
PART 4 Configure Putty Line discipline options
With default setting, we can see what we input in Putty pseudo terminal for the echo shell example above. All we need to do is configure the line discipline options, as follows.
References:
[1] http://www.programcreek.com/java-api-examples/index.php?api=org.apache.sshd.SshServer
[2] http://caffeineiscodefuel.blogspot.hk/2013/04/apache-mina-sshd-publickeyauthenticator.html
[3] http://stackoverflow.com/questions/15372360/apache-sshd-public-key-authentication
[4] https://tools.ietf.org/html/rfc4254#section-8