I'm trying to open an SSH client session from my Android app. Trying to connect to a device on the local network (a Raspberry Pi). I'm using the SSHJ library version 0.10.0. It fails on the ssh.connect() call, with a TransportException which is ultimately caused by a NoSuchAlgorithmException. Refer exception tree below.
SSHClient ssh = new SSHClient(new AndroidConfig());
Session session = null;
try {
//ssh.loadKnownHosts();
// Exception thrown on this line
ssh.connect("192.168.1.109", 22);
// Doesn't reach below
ssh.authPassword("user", "password");
session = ssh.startSession();
}
catch (net.schmizz.sshj.transport.TransportException ex) {
;
}
Exception tree:
net.schmizz.sshj.transport.TransportException
net.schmizz.sshj.common.SSHException
net.schmizz.sshj.common.SSHRuntimeException
java.security.GeneralSecurityException: java.security.NoSuchAlgorithmException: KeyFactory ECDSA implementation not found
java.security.NoSuchAlgorithmException: KeyFactory ECDSA implementation not found
Other system info:
SSHJ library : v0.10.0
Android device : Galaxy Note 3 running Android 4.4.2
I used the maven dependency support in Android Studio to bring in the SSHJ JAR and it pulled in the following three libraries in addition to the SSHJ v0.10.0 jar...
bouncy castle...
bcpkix-jdk15on-1.50.jar
bcprov-jdk15on-1.50.jar
logging....
slf4j-api-1.7.7.jar
Don't have a clue where to start with this exception ... any suggestions appreciated! Thanks.
UPDATE: 31-Oct-2014
As suggested by LeeDavidPainter, I included the SpongyCastle 1.51.0 JAR and added this line at the top:
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
I'm now getting a different exception on the same line:
net.schmizz.sshj.transport.TransportException
net.schmizz.sshj.common.SSHException
net.schmizz.sshj.common.SSHRuntimeException
java.security.GeneralSecurityException: java.security.spec.InvalidKeySpecException: key spec not recognised
java.security.spec.InvalidKeySpecException: key spec not recognised
Also note I tried the following line as well, with the same result:
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
I have another app on my phone which is basically doing exactly what I want to achieve - its called RaspberryPiController - it connects to your RPi over SSH with username and password auth. This works fine, so it would seem its not a network issue.
Android ships with a cut down version of BouncyCastle which does not include the ECDSA algorithms. So even though you include the full version in your class path, the Android runtime version will be picked up and used.
You may want to look at http://rtyley.github.io/spongycastle/ which was created to get around this, its a repackaged version of Bouncycastle that can be installed as a separate JCE provider in Android. Just install it as the default JCE provider before you try to connect with SSHJ (untested).
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
First add this BouncyCastle library in app/build.gradle file:
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
Then in your activity file, add a static block to remove the default BouncyCastle provider found in Android with our version:
static {
Security.removeProvider("BC");//first remove default os provider
Security.insertProviderAt(new BouncyCastleProvider(), 1);//add new provider
}
This will resolve the algorithm implementation not found issue.
Downgrade to sshj 0.9.0 here: http://mvnrepository.com/artifact/net.schmizz/sshj/0.9.0
The problem seems to have been introduced in 0.10.x. Also, I have tried the other JCE provider but got into the same trouble.
Jsch most likely worked because it does not support the Elliptic Curve algorithms for SSH AFAIK. If you don't need Elliptic Curve algorithms then that's your answer.
Couldn't get anywhere with this issue in SSHJ, so decided to give JSch a try which offers the same functionality. Its available as a maven repo as well - I used jsch version 0.1.51 ('com.jcraft:jsch:0.1.51').
It worked first time with this code fragment;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import java.io.ByteArrayOutputStream;
import java.util.Properties;
JSch jsch = new JSch();
com.jcraft.jsch.Session session = null;
String result = "";
try {
session = jsch.getSession("user", "192.168.1.109", 22);
session.setPassword("password");
// Avoid asking for key confirmation
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);
session.connect();
// SSH Channel
ChannelExec channel = (ChannelExec)session.openChannel("exec");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
channel.setOutputStream(stream);
// Execute command
channel.setCommand("ls -ltr");
channel.connect(1000);
java.lang.Thread.sleep(500); // this kludge seemed to be required.
channel.disconnect();
result = stream.toString();
}
catch (JSchException ex) {
String s = ex.toString();
System.out.println(s);
}
catch (InterruptedException ex) {
String s = ex.toString();
System.out.println(s);
}
finally {
if (session != null)
session.disconnect();
}
It feels like a more robust implementation when using it compared to SSHJ - or this impression might be caused by them selecting quite conservative timeouts. For example, if the target device is switched off, the session.connect() call will, by default, keep trying to connect for something like 20 seconds before giving up.
Based off of LeeDavidPainter's solution,
/**
* Creates a new SSH client stub
*/
public SSH(final String host, final int port)
{
SecurityUtils.setSecurityProvider(SecurityUtils.BOUNCY_CASTLE); //<-- Here
Security.insertProviderAt(new BouncyCastleProvider(), 1); //<-- Here
this.ssh.addHostKeyVerifier(new PromiscuousVerifier());
this.shell = new SSHShellSession();
this.ssh = new SSHClient();
this.connected = false;
this.initiated = false;
this.host = host;
this.port = port;
}
The two commented areas above //<-- here are the solution.
Related
It is a known problem to use the Java FTPSClient of Apache commons-net with session resumption. Session resumption is a security feature which a FTPS server can require for data connections. The Apache FTPSClient does not support session resumption, and the JDK APIs make it hard to build a custom implementation. There are a couple of workarounds using reflection, see e.g. this answer and this commons-net bug entry.
I use such a workaround (see snipped below) in JDK 11 and tested it against a local FileZilla Server. It works with FileZilla Server 0.9.6, but it doesn't with FileZilla Server 1.2.0, which is the latest version at the time of writing. With that version, when trying to establish a data connection, the server responds with:
425 Unable to build data connection: TLS session of data connection not resumed.
As I said, FileZilla Server 0.9.6 is fine with how I do session resumption, and I made sure that the setting for requiring session resumption is activated.
In FileZilla Server 1.2.0, such settings are now set implicitly and cannot be changed via the GUI, maybe not at all. Are there some server settings that I can tweak for this to work? Or is it an issue with how I implemented the workaround? Does anyone experience similar issues?
This is the workaround I am using:
public class FTPSClientWithSessionResumption extends FTPSClient {
static {
System.setProperty("jdk.tls.useExtendedMasterSecret", "false");
System.setProperty("jdk.tls.client.enableSessionTicketExtension", "false");
}
#Override
protected void _connectAction_() throws IOException {
super._connectAction_();
execPBSZ(0);
execPROT("P");
}
#Override
protected void _prepareDataSocket_(Socket socket) throws IOException {
if (useSessionResumption && socket instanceof SSLSocket) {
// Control socket is SSL
final SSLSession session = ((SSLSocket)_socket_).getSession();
if (session.isValid()) {
final SSLSessionContext context = session.getSessionContext();
try {
final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
sessionHostPortCache.setAccessible(true);
final Object cache = sessionHostPortCache.get(context);
final Method putMethod = cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
putMethod.setAccessible(true);
Method getHostMethod;
try {
getHostMethod = socket.getClass().getMethod("getPeerHost");
}
catch (NoSuchMethodException e) {
// Running in IKVM
getHostMethod = socket.getClass().getDeclaredMethod("getHost");
}
getHostMethod.setAccessible(true);
Object peerHost = getHostMethod.invoke(socket);
InetAddress iAddr = socket.getInetAddress();
int port = socket.getPort();
putMethod.invoke(cache, String.format("%s:%s", peerHost, port).toLowerCase(Locale.ROOT), session);
putMethod.invoke(cache, String.format("%s:%s", iAddr.getHostName(), port).toLowerCase(Locale.ROOT), session);
putMethod.invoke(cache, String.format("%s:%s", iAddr.getHostAddress(), port).toLowerCase(Locale.ROOT), session);
}
catch (Exception e) {
throw new IOException(e);
}
}
else {
throw new IOException("Invalid SSL Session");
}
}
}
}
The address under which the socket is cached is determined using getPeerHost, getInetAddress().getHostName(), and getInetAddress().getHostAddress(). I tried several combinations of doing or not doing these three, but I always get the same result.
Edit:
Here is a screenshot of the server logs of the full session:
As stated in this StackOverflow post it is possible to tell the JVM that only TLS 1.2 should be used.
Here is the link to the original answer which worked for me: command for java to use TLS1.2 only
You have to add a command line parameter at the start of the JVM in this case this is: java -Djdk.tls.client.protocols=TLSv1.2 -jar ... <rest of command line here>
This simple parameter worked for me, now I can connect and transfer data from a FTP-Server wich runs FileZilla FTP-Server 1.3.0
I have a code which calls a rest api protected with certificate, and that code was working for some time without issues, until I migrate application from Wildfly 10 to Wildfly 16.
The code itself is straight forward, it creates http request and set custom socket factory: `
private SSLSocketFactory getSSLSocketFactory() {
char[] certPassword = {}; // password hidden from you
try {
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final SSLContext sslContext = SSLContext.getInstance("TLS");
final KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fileInputStream = new FileInputStream(new File("cert.pk12"))) {
keyStore.load(fileInputStream, certPassword);
} catch (final Exception e) {
logger.error("....", e);
}
this.readLoadCertificateFile(keyStore);
kmf.init(keyStore, certPassword);
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{new AnyTrust()}, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
logger.error(".....", e);
}
throw new IllegalStateException("....");
}
HTTPRequest req = ....
req.setSSLSocketFactory(getSSLSocketFactory());
tokenHttpResp = req.send();`
`
All seems good but when I run this code from within WF16 it throws
IOException: Failed to load .p12 keystore:C:\Cert\cert.p12; error
constructing MAC: java.lang.SecurityException: JCE cannot authenticate
the provider BC;
org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad::-1
java.security.KeyStore.load in KeyStore.java::1445
I checked for the bouncy castle lib, and I don't have it in the application itself...
If any one knows what the issue could be I would appreciate any input.
Alternatively, I'm looking to move this socket factory creation in to the container itself, seems like Wildfly Elytron subsystem specifically designed for this, is it a good idea?
Answering my own question.
The error message like "JCE cannot authenticate the provider BC" indicating that the jar file, the Security Provider is loaded from, cannot be verified by JVM. Either the jar is not signed or signature cannot be verified. In my case, the newer Wildfly version has a newer version of bouncy castle library, which for some reason, cannot be verified by Java 8. Interesting enough, that it is fine with Java 10. Some people on the Internet says that this issue only occurs in Oracle's JVM and does not exist for Open JDK, I haven't tested it, just think it worth to mention.
To overcome issue you need to tell JVM to trust the security provider, for that, make sure that the Security Provider you want to use/JVM decided to use, mentioned in jre/lib/security/java.security file, it should have line like:
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
Then copy Jars with Security Provider into /jre/lib/ext folder
Can anybody tell how we interact with security enables Riak KV Cluster using java client.
I tried with following java code to interact with security enabled riak cluster, but getting SSLEngine Problem, Below is the java code ......
InputStream inputStream = null;
KeyStore ks = null;
try {
inputStream = new FileInputStream("/etc/ssl/certs/rootCA.pem");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) certFactory.generateCertificate(inputStream);
inputStream.close();
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, "password".toCharArray());
ks.setCertificateEntry("cacert", caCert);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (RiakConfig.class) {
List<RiakNode> riakNodeList = new ArrayList<RiakNode>();
for (final String riakServer : riakServerArray) {
RiakNode node = new RiakNode.Builder()
.withMinConnections(10)
.withMinConnections(50)
.withRemoteAddress(riakServer.split(":")[0])
.withRemotePort(Integer.parseInt(riakServer.split(":")[1]))
.withAuth("riakuser", "riakuser", ks)
.build();
riakNodeList.add(node);
}
cluster = new RiakCluster.Builder(riakNodeList).build();
cluster.start();
}
suggenst anyone how we do that???
getting SSLEngine Problem
Please, always provide the specific problem you are getting. "getting SSLEngine Problem" is a useless bit of information.
Based on this document it appears you are using the correct steps. Without more information about the security issue you are getting, further help is impossible. Most likely you do not have your certificates set up correctly. The RabbitMQ documentation includes a comprehensive TLS/SSL troubleshooting guide that can help you determine if your certificates were created correctly.
Additionally, I suggest that you review how the Riak Java Client sets up certificates and then uses them.
This part of the Makefile is where certs are imported with keytool:
https://github.com/basho/riak-java-client/blob/develop/Makefile#L43-L62
This is a class that uses the key store to create connections for use in tests:
https://github.com/basho/riak-java-client/blob/develop/src/test/java/com/basho/riak/client/core/operations/itest/RiakJKSConnection.java
Achually this is working fine for single node, in cluster i am facing SSL Engine Problem.
How to connect to a remote machine with username and password using sshj java api?
I tried this code. What is the problem with this code?
final SSHClient ssh = new SSHClient();
ssh.connect("192.168.0.1");
ssh.authPassword("abcde", "fgh".toCharArray());
try {
final Session session = ssh.startSession();
try {
final Command cmd = session
.exec("cd /home/abcde/Desktop/");
System.out.println(IOUtils.readFully(cmd.getInputStream())
.toString());
cmd.join(5, TimeUnit.SECONDS);
System.out.println("\n** exit status: " + cmd.getExitStatus());
} finally {
session.close();
}
} finally {
ssh.disconnect();
}
It is throwing this following error.
net.schmizz.sshj.transport.TransportException:
[HOST_KEY_NOT_VERIFIABLE] Could not verify ssh-rsa host key with
fingerprint ******** for 192.168.0.1 on port 22
You solve your problem by implementing HostKeyVerifier
class NullHostKeyVerifier implements HostKeyVerifier {
#Override
public boolean verify(String arg0, int arg1, PublicKey arg2) {
return true;
}
}
and adding this fake implementation to your SSHClient instance configuration:
...
final SSHClient ssh = new SSHClient();
ssh.addHostKeyVerifier(new NullHostKeyVerifier());
...
Insert ssh.loadKnownHosts(); or ssh.loadKnownHosts("somepath"); after instantiation of SSHClient.
Then add the machine (remote) you are trying to connect (192.168.0.1) to known_hosts file (on your machine) at the default location or at "somepath". For a Linux box default path will be /home/myuser/.ssh/known_hosts, or in a windows box c:/user/myuser/.ssh/known_hosts.
known_host is in openSSH format (ip/orhostname algorithm key commentary).
To add the machine to known_hosts:
-if you are using Linux (on your machine), just ssh to the remote machine and it will be automatically add to known_hosts.
-if you are using Windows, use bitwise tunnelier to connect to the remote machine it will store the key. Go to bitwise key manager (it will be on your start menu, bitwise folder) and export the row with the remote machine ip to openSSH format. Copy the resulting line to your known_host file.
That way you will be really validating the host key. It is also helpful in mule esb where you cannot add the nullhost verifier to the ssh connector (My case).
You miss ssh key, simply add
ssh.addHostKeyVerifier("10:20......");
where 10:20... from your exception: "with fingerprint ********"
import com.jcraft.jsch.*;
import com.jcraft.jsch.JSchException;
import oracle.jdbc.driver.OracleDriver;
import java.io.*;
import java.util.*;
import java.sql.*;
import java.net.*;
public class SecureFTP {
public static void main(String[] args) throws IOException , ClassNotFoundException, JSchException, SftpException{
JSch jsch = new JSch();
File file = new File("/home/xxxxx/.ssh/id_rsa");
Session session = null;
URL keyFileURL = null;
URI keyFileURI = null;
if (file.exists())
{
keyFileURL = file.toURL();
if (keyFileURL == null)
{
System.out.println("what");
throw new RuntimeException("Key file not found in classpath");
}
}
else System.out.println("FIle not found");
try{
keyFileURI = keyFileURL.toURI();
}
catch(Exception URISyntaxException)
{
System.out.println("Wrong URL");
}
String privateKey = ".ssh/id_rsa";
//jsch.addIdentity(privateKey);
jsch.addIdentity(new File(keyFileURI).getAbsolutePath());
System.out.println(new File(keyFileURI).getAbsolutePath() + " LOL");
session = jsch.getSession("username", "servername");
//session.setPassword("password");
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// connect
session.connect();
// get SFTP channel
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp schannel = (ChannelSftp) channel;
FileInputStream fis = new FileInputStream(sourcefile);
schannel.put(fis, destinationfile );
fis.close();
}
schannel.exit();
session.disconnect();
}
}
As you can see from the code I have commented out, I have tried everything possible to get this working and the only thing that works is if I set the password directly. I am trying to use the RSA private key generated, but I keep getting an auth fail.
I have added the public key to the list of authorized keys on the target server. And there is no passphrase.
Is there something else I am supposed to do? Like say, while generating the keys? Is there a step I am missing?
Is there another library I can use to implement the same function?
Make sure the necessary files exist (id_rsa and id_rsa.pub on the client, authorized_keys on the server). Make sure you can use public key authentication with another tool, like ssh, using these files.
If that looks alright, the problem may be with your Java security provider. Read on if you think you have the right files in place.
There are different formats for RSA private key storage, and SSH uses one that is not standard. Most providers expect something called a CRT RSA key, and when JSch doesn't give them a key in that format, they raise an exception which JSch silently eats and goes on to the next authentication method.
What is your provider? The following snippet will help you find out:
import java.security.KeyFactory;
…
KeyFactory f = KeyFactory.getInstance("RSA");
System.out.println(f.getProvider().getName());
Update: I did some checking around, and as of Java 5, the SunPKCS11 provider is installed with the highest precedence on Solaris systems, for performance. Since I don't run Solaris, I can't test it, but I believe this may be causing the problem.
JSch doesn't allow you to specify the provider to use for this operation through its API, so you will have to change the precedence of the installed providers. In fact, I'd suggest trying to remove the SunPKCS11 from this application; run this code once when your application starts up:
Security.removeProvider("SunPKCS11-Solaris");
Have you have copied the key into the file $HOME/.ssh/authorized_keys on the target server? If so, you should probably mention that. If not, that is required for this to work. Also, are you generating the key without a password? If the private key is password protected, you will need to provide that password to addIdentity.
After verifying those things, I'd recommend trying to connect via the command line using OpenSSH, as the Java code you have here looks correct. If the command line does not work, invoke it with -vvv to get verbose output about what it is doing. It is possible that the server is configured with PubkeyAuthentication set to no.