Skip to content

Latest commit

History

99 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

SSHAPI

SSHAPI is a clean and modern API for accessing SSH servers in Java. However, it does not supply the support for the protocol itself, or any encryption code, it instead delegates this to an SSH Provider, which in turn uses one of many already available SSH libraries.

try (SshClientclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(SftpClientsftp = client.sftp()) {
sftp.get("/home/myhome/stuff.txt", newFile("stuff.txt"));
}
}

It was originally written with two purposes in mind.

  • We wanted a way to compare SSH APIs for performance to highlight issues in our own products.
  • An abstraction layer for our UniTTY product to work with multiple SSH libraries.

However, it is now a viable complete SSH api with other advantages :-

  • Adds a modern API over the top of legacy APIs (e.g. try-with-resource support and non-blocking I/O).
  • If a new cipher becomes fashionable, and one provider implements it first, in many cases you can simply switch to it with no code changes.
  • If a vulnerability is discovered in one library, switch to another, again just by swapping the provider bridge in use.
  • If you have different performance or security requirements based on configuration, you can leave the choice to users
  • Different SSH libraries have different licenses, give yourself or your users choices based on these concerns.
  • You can compare yourself the performance or behaviour of one API against another.
  • Get near native SSH performance using the libssh provider (work in progress).
  • If the provider supports non-blocking usage, it will be used, otherwise SSHAPI will simulate non-blocking usage.

Requirements

  • Java. As of version 2.0.0, SSHAPI requires Java 11
  • Maven if building from source.

Installation

Installation of SSHAPI is no harder than any other SSH library for Java. Using your chosen build tool (below we use Maven), add the appropriate Provider Bridge library to your project, and all of the appropriate dependencies will be pulled in.

 <dependency>
<groupId>com.sshtools</groupId>
<artifactId>sshapi-maverick-synergy</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Providers include :-

  • sshapi-maverick-synergy (Modern, open source JAdaptive Maverick Synergy API)
  • sshapi-maverick-synergy-hotfixes (Commercially supported version of Maverick Synergy)
  • sshapi-maverick16 (Commercial, Legacy JAdaptive Maverick API)
  • sshapi-sshj (Currently well maintained open source SSHJ API)
  • sshapi-jsch (Now uses an updated fork of this well established API)
  • sshapi-trilead (A fork of Ganymed, itself now apparently unmaintained)
  • sshapi-ganymed (Now apparently unmaintained)
  • sshapi-libssh (experimental)
  • sshapi-openssh (incomplete experimental)

NOTE, if you are using SNAPSHOT versions of the library, you will need to add the Sonatype OSS snapshot repository too.

	<repository>
<id>oss-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots />
</repository>

Usage

For full usage, see the examples.

Connecting, Authenticating and Creating A Shell

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varshell = client.shell()) {
varin = shell.getInputStream();
varout = shell.getOutputStream();
// Do something with I/O streams
}
}

Connecting, Authenticating and Creating A Shell with a Pseudo Tty

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varshell = client.shell("xterm", 80, 24, 0, 0, null)) {
varin = shell.getInputStream();
varout = shell.getOutputStream();
// Do something with I/O streams
}
}

Getting A Remote File using SFTP

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varsftp = client.sftp()) {
sftp.get("/home/myhome/stuff.txt", newFile("stuff.txt"));
}
}

Uploading A File using SCP

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varscp = client.scp()) {
scp.put("remote-name", null, newFile("stuff.txt"), false);
}
}

Listing A Directory

There are a few ways of doing this.

As A Complete Array

All files will be loaded into memory at once.

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varsftp = client.sftp()) {
for(varfile : sftp.ls("/home/myhome/stuff.txt")) {
System.out.println(file.getName());
}
}
}

As A Stream

For iterating over large directories.

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varsftp = client.sftp()) {
try(varstream = client.directory("/home/myhome")) {
for(varfile : stream) {
System.out.println(file.getName());
} }
}
}

Recursively

Use a FileVisitor to recursively process an entire tree of files.

try (var client = Ssh.open("me@localhost", new ConsolePasswordAuthenticator())) {
try(var sftp = client.sftp()) {
sftp.visit("/home/myhome", new SftpFileVisitor() {
@Override
public FileVisitResult visitFile(SftpFile file, BasicFileAttributes attrs) throws IOException {
System.out.println(file.getName());
return FileVisitResult.CONTINUE;
}
});
}
}

Random Access Read And Write

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varsftp = client.sftp()) {
// Write test filetry (varhandle = sftp.file("test-file.txt", Mode.SFTP_WRITE, Mode.SFTP_CREAT)) {
ByteBufferbuf = ByteBuffer.allocate(16);
buf.putInt(1);
buf.putInt(2);
buf.putInt(3);
buf.putInt(4);
buf.flip();
handle.writeTo(buf);
}
// Read 4 bytes from test file from 4th byte, which is the 2nd 'int' written abovetry (varhandle = sftp.file("test-file.txt", Mode.SFTP_READ)) {
ByteBufferbuf = ByteBuffer.allocate(4);
handle.position(4).readFrom(buf);
if(buf.getInt(0) != 2)
thrownewIllegalStateException("Expected to receive value of 2.");
}
}
}

Run A Remote Command And Get The Output

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varcommand = client.command("ls /etc")) {
Util.joinStreams(command.getOutputStream(), System.out);
System.out.println("Exited with code: " + command.exitCode());
}
}

Start A Local Port Forward, Giving You Access To Remote TCP Service (e.g. web server)

try (varclient = Ssh.open("me@localhost", newConsolePasswordAuthenticator())) {
try(varcommand = client.localForward("0.0.0.0", 8443, "someremoteaddress", 443)) {
// Simply sleep to keep the tunnel open. Thread.sleep(600000);
}
}

Authenticating Using a Public/Private Keys

// Prompt for location of private keyvarpemFile = newFile(Util.prompt("Private key file",
System.getProperty("user.home") + File.separator + ".ssh" + File.separator + "id_rsa"));
try (varclient = Ssh.open("me@localhost", newDefaultPublicKeyAuthenticator(newConsolePasswordAuthenticator(), pemFile))) {
// Do SSH stuff
}

Custom Configuration

varconfig = newSshConfiguration();
config.addRequiredCapability(Capability.SFTP);
config.setPreferredServerToClientMAC("hmac-sha1");
config.setHostKeyValidator(newConsoleHostKeyValidator());
config.setBannerHandler(newConsoleBannerHandler());
// Create the client using that configuration and connect and authenticatetry (varclient = config.open("me@localhost", newConsolePasswordAuthenticator()) {
// Do SSH stuff
}

Multiple Providers

// List all of the providers and allow the user to select onevarproviders = DefaultProviderFactory.getAllProviders();
System.out.println("Providers :-");
for (inti = 0; i < providers.length; i++) {
System.out.println(" " + (i + 1) + ": " + providers[i].getClass().getName());
}
varprovider = providers[Integer.parseInt(Util.prompt("\nEnter the number for the provider you wish to use (1-"
+ providers.length + ")")) - 1];
// Create a client using that providertry (varclient = provider.open(newSshConfiguration(), "me@localhost", newConsolePasswordAuthenticator()) {
// Do SSH stuff
}

Changing A Private Key Passphrase

// Need a provider that does IDENTITY_MANAGEMENTvarconfig = newSshConfiguration();
config.addRequiredCapability(Capability.IDENTITY_MANAGEMENT);
// Create the provider, then identity manager using that configurationvarprovider = DefaultProviderFactory.getInstance().getProvider(config);
System.out.println("Got provider " + provider.getClass());
varmgr = provider.createIdentityManager(config);
// Read and (optionally) decrypt key	SshPrivateKeyFilepk;
varkeyFile = newFile("/home/myhome/.ssh/id_rsa");
try(varin = newFileInputStream(keyFile)) {
pk = mgr.createPrivateKeyFromStream(in); if (pk.isEncrypted()) {
varpw = Util.prompt("Old passphrase");
pk.decrypt(pw.toCharArray());
}
}
// Change passphrasepk.changePassphrase(newpw.toCharArray());
// Write the key back outtry(varfout = newFileOutputStream(keyFile)) {
fout.write(pk.getFormattedKey());
}

Create A Socket Factory That Is Tunneled To a Remote Host

varconfig = newSshConfiguration();
config.addRequiredCapability(Capability.TUNNELED_SOCKET_FACTORY);
// Connect, authenticatetry (varclient = config.open("me@localhost", newConsolePasswordAuthenticator())) {
varsf = client.createTunneledSocketFactory();
/* * Make a connection back to the SSH server we are connecting from and read the * first line of output. This could be any host that is accessible from the * remote SSH server, localhost:22 is just used as we know something will be * running there! */try(varsocket = sf.createSocket("localhost", 22)) {
varreader = newBufferedReader(newInputStreamReader(socket.getInputStream()));
System.out.println("SSH ident: " + reader.readLine());
}
}

Authenticating Using A Local SSH Agent

varconfig = newSshConfiguration();
config.addRequiredCapability(Capability.AGENT);
try (varclient = config.open("me@localhost", newDefaultAgentAuthenticator())) {
// Do SSH stuff
}

Non-blocking usage

SSHAPI can use a non-blocking pattern if you prefer that. For most methods, there will be an equivalent Later method. For example shell() has a shellLater(). These methods will not block, nor will they throw an exception. Instead, a Future is returned, that may be used to retrieve the shell object when it is ready, or cancel the operation. If an exception occurs during the operation, the future will also return that.

Non-blocking Shell

varfuture = Ssh.openLater("me@localhost", newConsolePasswordAuthenticator())
// At some point after this you can attempt to retrieve the SshClient instance from the future. This will block until it's available. You can request a timeout toovarclient = future.get(10, TimeUnit.SECONDS);
// Shells act in the same wayvarshellFuture = client.shellLater();
varshell = shellFuture.get(); // To handle data coming from the remote server, set the input handlershell.setInput((buffer) -> {
// Do something with ByteBuffer (buffer is pre-flipped to limit() will be length of data, position() will be zero
});
// To send data to the shell simple use writeLater(). You use the future to // wait for when this actually happens.varwriteFuture = shell.writeLater(ByteBuffer.wrap("Test!".getBytes()));
writeFuture.get();
// Now you can close, and of course, wait for the close to complete if you wantvarcloseShellFuture = shell.closeLater();
closeShellFuture.get();
// And finally close the client toovarcloseFuture = client.closeLater();
closeFuture.get();

About

SSHAPI is yet another Java library for talking to SSH servers. However, instead of directly supporting SSH, it is instead a facade for a number of supported providers, including Maverick, Ganymed, J2SSH and JSch. Think of it like SLF4J, for SSH!

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages