Fixing javax.net.ssl.SSLPeerUnverifiedException: No peer certificate - java

I am developing an android app that has to communicate with server over https with self signed certificate (SSL/TLS1.2). I am also using Volley.
I am following this tutorial. Saved the .crt file, created key.bks in raw directory with keytool -importcert -v -trustcacerts -file "cert.crt" -alias IntermediateCA -keystore "key.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-146.jar" -storetype BKS -storepass blabla
and so on.
When I am sending data to server, Response.ErrorListener() receives No peer certificate error.
Tried to send my json with Postman (of course, had to add the certificate) -worked fine.
Manually installed the certificate on my android device - worked fine
How can I make it work?

I fought a lot with this problem.
It appeared that the server I was sending to has a virtual host (hosted on GAE). On Android 5.0 this issue is solved, but bellow Android 5.0 you have to add SNI support yourself.
Here is an explanation of this problem http://blog.dev001.net/post/67082904181/android-using-sni-and-tlsv1-2-with-apache.
So to make my code work I had to change SslSocketFactory class from the tutorial. It did the magic.
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.params.HttpParams;
import android.annotation.TargetApi;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
class SslSocketFactory extends SSLSocketFactory {
InputStream mkeyStore;
String mkeyStorePassword;
public SslSocketFactory(InputStream keyStore, String keyStorePassword) throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException{
super(null);
mkeyStore=keyStore;
mkeyStorePassword=keyStorePassword;
}
#Override
public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException {
return null;
}
#Override
public Socket createSocket() throws IOException {
return null;
}
#Override
public boolean isSecure(Socket s) throws IllegalArgumentException {
if (s instanceof SSLSocket) {
return ((SSLSocket) s).isConnected();
}
return false;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
SSLSocket sslSocket = null;
if (autoClose) {
socket.close();
}
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0, null);
try {
sslSocketFactory.setTrustManagers(new TrustManager[] { new SsX509TrustManager( mkeyStore, mkeyStorePassword) });
} catch (GeneralSecurityException e1) {
e1.printStackTrace();
}
sslSocket = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port);
sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
sslSocketFactory.setHostname(sslSocket, host);
} else {
try {
java.lang.reflect.Method setHostnameMethod = sslSocket.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(sslSocket, host);
} catch (Exception e) {
}
}
return sslSocket;
}
}

Related

AD authentication with one LDAP failed but passed with another LDAP

From the following website, I found codes to perform java AD authentication.
http://java2db.com/jndi-ldap-programming/solution-to-sslhandshakeexception
The followings are the codes:
MySSLSocketFactory.java
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class MySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory socketFactory;
public MySSLSocketFactory() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());
socketFactory = ctx.getSocketFactory();
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
public static SocketFactory getDefault() {
return new MySSLSocketFactory();
}
#Override
public String[] getDefaultCipherSuites() {
return socketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return socketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {
return socketFactory.createSocket(socket, string, num, bool);
}
#Override
public Socket createSocket(String string, int num) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num);
}
#Override
public Socket createSocket(String string, int num, InetAddress netAdd, int i)
throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num, netAdd, i);
}
#Override
public Socket createSocket(InetAddress netAdd, int num) throws IOException {
return socketFactory.createSocket(netAdd, num);
}
#Override
public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {
return socketFactory.createSocket(netAdd1, num, netAdd2, i);
}
}
DummyTrustmanager.java
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class DummyTrustmanager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException
{
}
public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException
{
}
public X509Certificate[] getAcceptedIssuers()
{
return new java.security.cert.X509Certificate[0];
}
}
TestAD.java
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class TestAD {
public static void main(String[] args) {
try {
//String url = "ldaps://abc.company.com:636";
String url = "ldaps://xyz.group.com:636";
String conntype = "simple";
// String id = "abc#abc.company.com";
String id = "xyz.group.com";
//String password = "abcpassword";
String password = "xyzpassword";
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, url);
environment.put("java.naming.ldap.factory.socket", "MySSLSocketFactory");
environment.put(Context.SECURITY_AUTHENTICATION, conntype);
environment.put(Context.SECURITY_PRINCIPAL, id);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext ldapContext = new InitialDirContext(environment);
System.out.println("Bind successful");
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
My company has a few subsidiaries, and they have their own LDAPs. When I run TestAD against my company ABC's LDAP, it works fine. But when I run it against the subsidiary XYZ's LDAP, I got the following exception:
javax.naming.CommunicationException: simple bind failed: xyz.group.com:636
[Root exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.]
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:219)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2791)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:210)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:153)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:83)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
at javax.naming.InitialContext.init(InitialContext.java:244)
at javax.naming.InitialContext.<init>(InitialContext.java:216)
at javax.naming.directory.InitialDirContext.<init>(InitialDirContext.java:101)
at TestAD.main(TestAD.java:26)
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:931)
at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
at com.sun.jndi.ldap.Connection.run(Connection.java:877)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.
at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:214)
at sun.security.util.HostnameChecker.match(HostnameChecker.java:96)
at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:459)
at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:1125)
at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1092)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
... 12 more
What could be the reason for the failure? I did not import ABC's or XYZ's certificate into my trust store. Why does it work fine for ABC but not for XYZ? Could it be that XYZ is expecting my certificate?
This caused by the endpoint identification algorithms, it checks the host name in your configuration matches the host name in your remote LDAPS TLS server certificate, and that those host names are valid.
Given this error No subject alternative DNS name matching xyz.group.com found, it must be that "xyz.group.com" doesn't match your ldap server certificate, while "abc.company.com" does match.
Java 8u181 brought some changes in core-libs/javax.naming and updated how secure LDAP connections are handled (cf. release note) :
Endpoint identification has been enabled on
LDAPS connections.
To improve the robustness of LDAPS (secure LDAP over TLS) connections,
endpoint identification algorithms have been enabled by default.
Note that there may be situations where some applications that were
previously able to successfully connect to an LDAPS server may no
longer be able to do so. Such applications may, if they deem
appropriate, disable endpoint identification using a new system
property: com.sun.jndi.ldap.object.disableEndpointIdentification.
Define this system property (or set it to true) to disable endpoint
identification algorithms.
Disabling the endpoint identification algorithms is a workaround, the long term solution is to fix the server certificate so that it matches "xyz.group.com" hostname.

Websphere MQ SSL connection using java

I've below code which connects to IBM Websphere MQ that doesn't have SSL without issues and able to put/ get messages. But when I try to connect with a MQ with SSL, i'm getting error.
Please note that the patch() method is to disable SSL, which gets executed but isn't disabling SSL.
Rather than this, can you tell me how I can connect to the MQ that has SSL enabled channel.
Note - I've the certificate files like - key.kdb, key.crl, key.rdb, key.sth and few other CA3, CA4.cer files. How do I install them and use it in my code?
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.*;
import java.io.IOException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class mq1 {
public static String host = "localhost";
public static int port = 1414;
public static String strchannel = "MQ.CHL";
public static String strqueuemanager = "MQMGR";
public static String strqueue = "REQUEST.QUEUE";
#SuppressWarnings("deprecation")
public static void main(String[] args) throws KeyManagementException, NoSuchAlgorithmException {
//call patch() to skip SSL
patch();
int openOptions = CMQC.MQOO_BROWSE | CMQC.MQOO_INQUIRE | CMQC.MQOO_OUTPUT | CMQC.MQOO_INPUT_AS_Q_DEF ;
MQEnvironment.hostname = host;
MQEnvironment.port = port;
MQEnvironment.channel = strchannel;
MQEnvironment.properties.put(CMQC.TRANSPORT_PROPERTY,CMQC.TRANSPORT_MQSERIES);
MQQueueManager qMgr;
try {
qMgr = new MQQueueManager (strqueuemanager);
System.out.println(qMgr);
MQQueue destQueue = qMgr.accessQueue(strqueue, openOptions);
System.out.println("Queue size:" + destQueue.getCurrentDepth());
MQMessage hello_world = new MQMessage();
System.out.println("MQMessage message created");
hello_world.writeUTF("Sending Sample message");
MQPutMessageOptions pmo = new MQPutMessageOptions();
try {
destQueue.put(hello_world,pmo);
destQueue.get(hello_world);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
int len = hello_world.getDataLength();
System.out.println("Length : " + len);
System.out.println("GET: "+ hello_world.readString(len-1));
destQueue.close();
qMgr.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void patch() throws KeyManagementException, NoSuchAlgorithmException {
System.out.println("Calling SSL patch");
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
System.out.println("trustAllCerts = "+trustAllCerts);
SSLContext sc = SSLContext.getInstance("SSL");
System.out.println("sc before init = "+sc);
sc.init(
null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
return true;
}
};
System.out.println("sc after init= "+sc);
System.out.println("allHostsValid= "+allHostsValid);
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
}
}
Easiest way is not to put it in your code at all and instead utilise a Client Channel Definitiin Table (CCDT). Then you can use it without SSL and later change the CCDT to use SSL without needing to change your code at all. This is considered the best practice for client connectivity to IBM MQ.
This will mean you stop using the MQEnvironment class to pass in things like the channel name, hostname etc, and instead use a slightly different MQQueueManager constructor with two parameters, the first is the queue managers name as before, the second is the name of your CCDT file.
You can read more about how to use the CCDT with Java in the official IBM MQ Knowledge Center at the following page which also includes a link to how to create the CCDT file:-
Using a client channel definition table with IBM MQ classes for Java

WSImport over SSL with fake (dev) certificate

Recently I spent couple of hours trying to get WSImport working on web service that is hosted over HTTPS, with fake certificate (dev deployment).
I tried to use windows version of wsimport.exe, from Java 8 jdk.
I specified option -XdisableSSLHostnameVerification, but it kept complaining about wrong certificate.
That is true, certificate is not valid, but in dev environment it should be acceptable.
I did not find easy way to make wsimport to skip certificate check.
Finally I got a solution, using wrapper class.
I think it makes sense to share the solution.
Hope it will save some someone's time for better purposes.
The solution (assuming that java is installed in c:\Program Files\Java\jdk1.8.0_40):
How to compile
"c:\Program Files\Java\jdk1.8.0_40\bin\javac" -cp "c:\Program Files\Java\jdk1.8.0_40\lib\tools.jar" WSImportSSLByPass.java
How to use
"c:\Program Files\Java\jdk1.8.0_40\bin\java" -cp "c:\Program Files\Java\jdk1.8.0_40\lib\tools.jar";. WSImportSSLByPass %wsimport args%
The code
to be put into WSImportSSLByPass.java
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class WSImportSSLByPass {
public static void main(String[] args) throws Throwable{
configureBypassSSL();
com.sun.tools.internal.ws.WsImport.main(args);
}
private static void configureBypassSSL() throws NoSuchAlgorithmException,
KeyManagementException {
SSLContext ssl_ctx = SSLContext.getInstance("SSL");
TrustManager[] trust_mgr = get_trust_mgr();
ssl_ctx.init(null, // key manager
trust_mgr, // trust manager
new SecureRandom()); // random number generator
SSLSocketFactory sf = ssl_ctx.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sf);
HttpsURLConnection.setDefaultHostnameVerifier(new DummyHostVerifier());
}
private static TrustManager[] get_trust_mgr() {
TrustManager[] certs = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String t) {
}
public void checkServerTrusted(X509Certificate[] certs, String t) {
}
} };
return certs;
}
}
class DummyHostVerifier implements HostnameVerifier {
public boolean verify(String name, SSLSession sess) {
return true;
}
}
In case of 2-way ssl handshake, we can modify the WSImportSSLByPass class like this
import com.sun.tools.internal.ws.WsImport;
public class OCBWSImport {
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws Throwable {
// TODO code application logic here
//System.setProperty("javax.net.ssl.trustStore", "C:\\Program Files\\Java\\jdk1.8.0_131\\jre\\lib\\security\\cacerts");
//System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
//System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
//Certificate for 2-way handshake
System.setProperty("javax.net.ssl.keyStore", "D:\\tuanpa\\yourp12file.p12");
System.setProperty("javax.net.ssl.keyStorePassword", "password of p12 file");
//Hostname checking bypass
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier() {
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
//return hostname.equals("192.168.1.10");
return true;
}
});
WsImport.main(args);
}
}

How do I configure client authentication with generated certificate in apache-commons net

First off, I know there is a similar question here but it doesn't answer my doubts.
I have a FTPS server (vsftpd) configured with SSL.
I've generated the appropriate keys with:
openssl req -x509 -nodes -days 1825 -newkey rsa:2048 \
-keyout private/vsftpd.key \
-out certs/vsftpd.crt
And configured the respective conf file in the server.
Now, in the Java client code with Apache commons net, I'm able to communicate with the server over SSL with something like this:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
public class CommonsNetFTPSTest {
public static void main(String[] args) throws Exception {
System.setProperty("javax.net.debug", "ssl");
String server = "XXX.XXX.XXX.XXX";
String username = "USER_TEST";
String password = "ABCD1234";
String remoteFile = "/Data/Input/PH240819";
String localFile = "PH240819";
String protocol = "SSL"; // TLS / null (SSL)
int port = 990;
int timeoutInMillis = 10000;
boolean isImpicit = true;
FTPSClient client = new FTPSClient(protocol, isImpicit);
client.setDataTimeout(timeoutInMillis);
client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
System.out.println("################ Connecting to Server ################################");
try
{
int reply;
System.out.println("################ Connect Call ################################");
client.connect(server, port);
client.login(username, password);
System.out.println("################ Login Success ################################");
//client.setFileType(FTP.BINARY_FILE_TYPE);
client.setFileType(FTP.NON_PRINT_TEXT_FORMAT);
client.execPBSZ(0); // Set protection buffer size
client.execPROT("P"); // Set data channel protection to private
client.enterLocalPassiveMode();
System.out.println("Connected to " + server + ".");
reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
client.disconnect();
System.err.println("FTP server refused connection.");
System.exit(1);
}
client.listFiles();
boolean retrieved = client.retrieveFile(remoteFile, new FileOutputStream(localFile));
}
catch (Exception e)
{
if (client.isConnected())
{
try
{
client.disconnect();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
System.err.println("Could not connect to server.");
e.printStackTrace();
return;
}
finally
{
//client.disconnect();
client.logout();
System.out.println("# client disconnected");
}
}
}
But in that way my client is accepting any certificate, and I guess it's possible a man-in-the-middle attack. So I'd like to tell my client what certificates to accept, and only connect when the certificate is valid.
Now, I have both files generated previously: vsftpd.key and vsftpd.crt, and I've imported the crt file in a local keystore like this:
keytool -import -alias alias -file vsftps.crt -keypass keypass -keystore mykeystore.jks -storepass Hello1
My question is, How do I tell my client that use the certificate from mykeystore? what else am I missing?. Thanks
Ok, now I have it.
I was doing it wrong from the beginning. To start with, you need to convert the two files (vsftpd.crt and vsftpd.key) into a single PKCS12 file.
openssl pkcs12 -export -in vsftpd.crt -inkey vsftpd.key > vsftpd.p12
Next, you need to import the PKCS12 file into a keystore:
keytool -importkeystore -srckeystore vsftpd.p12 -destkeystore keystore.jks -srcstoretype pkcs12
Detailed instructions [here].2
Finally, you just need to instantiate a trust manager with the generated keystore, and hand it to the FTPSClient. Something like:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.io.Util;
import org.apache.commons.net.util.TrustManagerUtils;
public method() throws IOException, GeneralSecurityException{
File storeFile = new File("path/to/keystore");
KeyStore keyStore = loadStore("JKS", storeFile, "password");
X509TrustManager defaultTrustManager = TrustManagerUtils.getDefaultTrustManager(keyStore);
client = new FTPSClient(properties.getProtocol(), isImpicit);
client.setTrustManager(defaultTrustManager);
logOutput = new LogOutputStream(log, Level.INFO);
}
//Helper method from apache: http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/util/KeyManagerUtils.html
private KeyStore loadStore(String storeType, File storePath, String storePass)
throws KeyStoreException, IOException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(storeType);
FileInputStream stream = null;
try {
stream = new FileInputStream(storePath);
ks.load(stream, storePass.toCharArray());
} finally {
Util.closeQuietly(stream);
}
return ks;
}
you have to generate your own keystore from previous comment.
Now use this link https://issues.apache.org/jira/browse/NET-326
Find this comment (Bogdan Drozdowski added a comment - 10/Mar/11 15:16) and do FTPSCLient(SSLContext sslContext) constructor like in this comment, and your ftpsClient will work with certificate and private key auth.

Simple Java HTTPS server

I need to set up a really lightweight HTTPS server for a Java application. It's a simulator that's being used in our development labs to simulate the HTTPS connections accepted by a piece of equipment in the wild. Because it's purely a lightweight development tool and isn't used in production in any way at all, I'm quite happy to bypass certifications and as much negotiation as I can.
I'm planning on using the HttpsServer class in Java 6 SE but I'm struggling to get it working. As a test client, I'm using wget from the cygwin command line (wget https://[address]:[port]) but wget reports that it was "Unable to establish SSL connection".
If I run wget with the -d option for debugging it tells me "SSL handshake failed".
I've spent 30 minutes googling this and everything seems to just point back to the fairly useless Java 6 documentation that describes the methods but doesn't actually talk about how to get the darn thing talking or provide any example code at all.
Can anyone nudge me in the right direction?
What I eventually used was this:
try {
// Set up the socket address
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), config.getHttpsPort());
// Initialise the HTTPS server
HttpsServer httpsServer = HttpsServer.create(address, 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
// Initialise the keystore
char[] password = "simulator".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("lig.keystore");
ks.load(fis, password);
// Set up the key manager factory
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
// Set up the trust manager factory
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
// Set up the HTTPS context and parameters
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
// Initialise the SSL context
SSLContext c = SSLContext.getDefault();
SSLEngine engine = c.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
// Get the default parameters
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
params.setSSLParameters(defaultSSLParameters);
} catch (Exception ex) {
ILogger log = new LoggerFactory().getLogger();
log.exception(ex);
log.error("Failed to create HTTPS port");
}
}
});
LigServer server = new LigServer(httpsServer);
joinableThreadList.add(server.getJoinableThread());
} catch (Exception exception) {
log.exception(exception);
log.error("Failed to create HTTPS server on port " + config.getHttpsPort() + " of localhost");
}
To generate a keystore:
$ keytool -genkeypair -keyalg RSA -alias self_signed -keypass simulator \
-keystore lig.keystore -storepass simulator
See also here.
Potentially storepass and keypass might be different, in which case the ks.load and kmf.init must use storepass and keypass, respectively.
I updated your answer for a HTTPS server (not socket-based). It might help with CSRF and AJAX calls.
import java.io.*;
import java.net.InetSocketAddress;
import java.lang.*;
import java.net.URL;
import com.sun.net.httpserver.HttpsServer;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.*;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLConnection;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.net.InetAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;
public class SimpleHTTPSServer {
public static class MyHandler implements HttpHandler {
#Override
public void handle(HttpExchange t) throws IOException {
String response = "This is the response";
HttpsExchange httpsExchange = (HttpsExchange) t;
t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
t.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
/**
* #param args
*/
public static void main(String[] args) throws Exception {
try {
// setup the socket address
InetSocketAddress address = new InetSocketAddress(8000);
// initialise the HTTPS server
HttpsServer httpsServer = HttpsServer.create(address, 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
// initialise the keystore
char[] password = "password".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("testkey.jks");
ks.load(fis, password);
// setup the key manager factory
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
// setup the trust manager factory
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
// setup the HTTPS context and parameters
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
// initialise the SSL context
SSLContext context = getSSLContext();
SSLEngine engine = context.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
// Set the SSL parameters
SSLParameters sslParameters = context.getSupportedSSLParameters();
params.setSSLParameters(sslParameters);
} catch (Exception ex) {
System.out.println("Failed to create HTTPS port");
}
}
});
httpsServer.createContext("/test", new MyHandler());
httpsServer.setExecutor(null); // creates a default executor
httpsServer.start();
} catch (Exception exception) {
System.out.println("Failed to create HTTPS server on port " + 8000 + " of localhost");
exception.printStackTrace();
}
}
}
To create a self-signed certificate:
keytool -genkeypair -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048
With ServerSocket
You can use the class that HttpsServer is built around to be even more light-weight: ServerSocket.
Single-threaded
The following program is a very simple, single-threaded server listening on port 8443. Messages are encrypted with TLS using the keys in ./keystore.jks:
public static void main(String... args) {
var address = new InetSocketAddress("0.0.0.0", 8443);
startSingleThreaded(address);
}
public static void startSingleThreaded(InetSocketAddress address) {
System.out.println("Start single-threaded server at " + address);
try (var serverSocket = getServerSocket(address)) {
var encoding = StandardCharsets.UTF_8;
// This infinite loop is not CPU-intensive since method "accept" blocks
// until a client has made a connection to the socket
while (true) {
try (var socket = serverSocket.accept();
// Use the socket to read the client's request
var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), encoding.name()));
// Writing to the output stream and then closing it sends
// data to the client
var writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), encoding.name()))
) {
getHeaderLines(reader).forEach(System.out::println);
writer.write(getResponse(encoding));
writer.flush();
} catch (IOException e) {
System.err.println("Exception while handling connection");
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("Could not create socket at " + address);
e.printStackTrace();
}
}
private static ServerSocket getServerSocket(InetSocketAddress address)
throws Exception {
// Backlog is the maximum number of pending connections on the socket,
// 0 means that an implementation-specific default is used
int backlog = 0;
var keyStorePath = Path.of("./keystore.jks");
char[] keyStorePassword = "pass_for_self_signed_cert".toCharArray();
// Bind the socket to the given port and address
var serverSocket = getSslContext(keyStorePath, keyStorePassword)
.getServerSocketFactory()
.createServerSocket(address.getPort(), backlog, address.getAddress());
// We don't need the password anymore → Overwrite it
Arrays.fill(keyStorePassword, '0');
return serverSocket;
}
private static SSLContext getSslContext(Path keyStorePath, char[] keyStorePass)
throws Exception {
var keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keyStorePath.toFile()), keyStorePass);
var keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, keyStorePass);
var sslContext = SSLContext.getInstance("TLS");
// Null means using default implementations for TrustManager and SecureRandom
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
return sslContext;
}
private static String getResponse(Charset encoding) {
var body = "The server says hi 👋\r\n";
var contentLength = body.getBytes(encoding).length;
return "HTTP/1.1 200 OK\r\n" +
String.format("Content-Length: %d\r\n", contentLength) +
String.format("Content-Type: text/plain; charset=%s\r\n",
encoding.displayName()) +
// An empty line marks the end of the response's header
"\r\n" +
body;
}
private static List<String> getHeaderLines(BufferedReader reader)
throws IOException {
var lines = new ArrayList<String>();
var line = reader.readLine();
// An empty line marks the end of the request's header
while (!line.isEmpty()) {
lines.add(line);
line = reader.readLine();
}
return lines;
}
Here's a project using this socket-based approach.
Multi-threaded
To use more than one thread for the server, you can employ a thread pool:
public static void startMultiThreaded(InetSocketAddress address) {
try (var serverSocket = getServerSocket(address)) {
System.out.println("Started multi-threaded server at " + address);
// A cached thread pool with a limited number of threads
var threadPool = newCachedThreadPool(8);
var encoding = StandardCharsets.UTF_8;
// This infinite loop is not CPU-intensive since method "accept" blocks
// until a client has made a connection to the socket
while (true) {
try {
var socket = serverSocket.accept();
// Create a response to the request on a separate thread to
// handle multiple requests simultaneously
threadPool.submit(() -> {
try ( // Use the socket to read the client's request
var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), encoding.name()));
// Writing to the output stream and then closing it
// sends data to the client
var writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), encoding.name()))
) {
getHeaderLines(reader).forEach(System.out::println);
writer.write(getResponse(encoding));
writer.flush();
// We're done with the connection → Close the socket
socket.close();
} catch (Exception e) {
System.err.println("Exception while creating response");
e.printStackTrace();
}
});
} catch (IOException e) {
System.err.println("Exception while handling connection");
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("Could not create socket at " + address);
e.printStackTrace();
}
}
private static ExecutorService newCachedThreadPool(int maximumNumberOfThreads) {
return new ThreadPoolExecutor(0, maximumNumberOfThreads,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>());
}
Create a certificate
Use the keytool to create a self-signed certificate (you can get a proper certificate from Let's Encrypt for free):
keytool -genkeypair -keyalg RSA -alias selfsigned -keystore keystore.jks \
-storepass pass_for_self_signed_cert \
-dname "CN=localhost, OU=Developers, O=Bull Bytes, L=Linz, C=AT"
Contact the server
After starting the server, connect to it with curl:
curl -k https://localhost:8443
This will fetch a message from the server:
The server says hi 👋
Inspect which protocol and cipher suite were established by curl and your server with
curl -kv https://localhost:8443
Using JDK 13 and curl 7.66.0, this produced
SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
Refer to Java Network Programming by Elliotte Rusty Harold for more on the topic.
Although this question is really old, someone mentioned me this topic and asked if it could be simplified. Most of the answers demonstrate very well how to setup a simple https server with sun, but I want to provide an alternative which is hopefully a bit easier.
For this setup I am assuming you already have the keystore and truststore in place.
The rest endpoint:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class HelloWorldController implements HttpHandler {
#Override
public void handle(HttpExchange exchange) throws IOException {
try (OutputStream responseBody = exchange.getResponseBody()) {
exchange.getResponseHeaders().set("Content-Type", "text/plain");
String payload = "Hello";
exchange.sendResponseHeaders(200, payload.length());
responseBody.write(payload.getBytes(StandardCharsets.UTF_8));
}
}
}
Server configuration:
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
import nl.altindag.server.controller.HelloWorldController;
import nl.altindag.ssl.SSLFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class App {
public static void main(String[] args) throws IOException {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial("keystore.jks", "secret".toCharArray())
.withTrustMaterial("truststore.jks", "secret".toCharArray())
.build();
InetSocketAddress socketAddress = new InetSocketAddress(8443);
HttpsServer httpsServer = HttpsServer.create(socketAddress, 0);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslFactory.getSslContext()) {
#Override
public void configure(HttpsParameters params) {
params.setSSLParameters(sslFactory.getSslParameters());
}
});
httpsServer.createContext("/api/hello", new HelloWorldController());
httpsServer.setExecutor(Executors.newCachedThreadPool());
httpsServer.start();
}
}
I need to add some disclaimer here... I use SSLFactory class from the Github - SSLContext-Kickstart library to easily construct a SSLContext. It is maintained by me. You don't need to use it as others have provided a way to construct it with just plain java.
Just a reminder to others: com.sun.net.httpserver.HttpsServer in the solutions above is not part of the Java standard. Although it is bundled with the Oracle/OpenJDK JVM, it is not included in all JVMs so this will not work out of the box everywhere.
There are several lightweight HTTP servers out there that you can embed in your application that support HTTPS and run on any JVM.
One of them is JLHTTP - The Java Lightweight HTTP Server which is a tiny one-file server (or ~50K/35K jar) with no dependencies. Setting up the keystore, SSLContext etc. is similar to the above, since it also relies on the standard JSSE implementation, or you can specify the standard system properties to configure SSL. You can see the FAQ or the code and its documentation for details.
Disclaimer: I'm the author of JLHTTP. You can check it out for yourself and determine if it suits your needs. I hope you find it useful :-)

Categories

Resources