I have 2 apps, one is a Servlet/Tomcat Server, and the other is an Android app.
I want to use HttpURLConnection to send and receive XML between both.
Code:
private String sendPostRequest(String requeststring) {
DataInputStream dis = null;
StringBuffer messagebuffer = new StringBuffer();
HttpURLConnection urlConnection = null;
try {
URL url = new URL(this.getServerURL());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("POST");
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(requeststring.getBytes());
out.flush();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
dis = new DataInputStream(in);
int ch;
long len = urlConnection.getContentLength();
if (len != -1) {
for (int i = 0; i < len; i++)
if ((ch = dis.read()) != -1) {
messagebuffer.append((char) ch);
}
} else {
while ((ch = dis.read()) != -1)
messagebuffer.append((char) ch);
}
dis.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
urlConnection.disconnect();
}
return messagebuffer.toString();
}
Now, I need to use SSL to send the XMLs for security.
First, I use Java Keytool to generate the .keystore file.
Keytool -keygen -alias tomcat -keyalg RSA
Then I put the XML Code on server.xml file of Tomcat to use SSL
<Connector
port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
keystoreFile="c:/Documents and Settings/MyUser/.keystore"
keystorePass="password"
clientAuth="false" sslProtocol="TLS"
/>
Then, I change it the HttpURLConnection for HttpsURLConnection
private String sendPostRequest(String requeststring) {
DataInputStream dis = null;
StringBuffer messagebuffer = new StringBuffer();
HttpURLConnection urlConnection = null;
//Conexion por HTTPS
HttpsURLConnection urlHttpsConnection = null;
try {
URL url = new URL(this.getServerURL());
//urlConnection = (HttpURLConnection) url.openConnection();
//Si necesito usar HTTPS
if (url.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
//Creo la Conexion
urlHttpsConnection = (HttpsURLConnection) url.openConnection();
//Seteo la verificacion para que NO verifique nada!!
urlHttpsConnection.setHostnameVerifier(DO_NOT_VERIFY);
//Asigno a la otra variable para usar simpre la mism
urlConnection = urlHttpsConnection;
} else {
urlConnection = (HttpURLConnection) url.openConnection();
}
//Do the same like up
and add a trustAllHosts method to Trust every server (dont check for any certificate)
private static void trustAllHosts() {
X509TrustManager easyTrustManager = new X509TrustManager() {
public void checkClientTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}
public void checkServerTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {easyTrustManager};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
Those changes worked very good, but I don´t want to Trust every server. I want to use my keystore file to validate the connection and use SSL in the right way.
I read a lot on the internet and made a lot of tests, but I can´t understand what I have to do and how to do it.
Can somebody help me?
Thank you very much
Sorry for my poor english
-------------------------UPDATE 2011/08/24-------------------------------------------------
Well, I'm still working on this. I made a new method to set the KeyStore, InputStream, etc
The method looks like this:
private static void trustIFNetServer() {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
String keyPassword = "password";
ks.load(in, keyPassword.toCharArray());
in.close();
tmf.init(ks);
TrustManager[] tms = tmf.getTrustManagers();
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tms, new java.security.SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
}
First I had a lot of problems with the Key and the Certificate, but now it is working (I think so)
My problem right now is a TimeOut Exception. I don´t know why it is generated. I'm think it's something with the data write, but I can't solve yet.
Any Idea?
You need to create a trust store file for your self-signed certificate as described here.
Use it on the client side to connect with your server. It doesn't really matter if you use JKS or another format, I'll assume JKS for now.
To accomplish what you have in mind you need a different TrustManager, obviously. You can use TrustManagerFactory and feed its trust settings with your newly created trust store.
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream in = new FileInputStream("<path to your key store>");
ks.load(in, "password".toCharArray());
in.close();
tmf.init(ks);
TrustManager[] tms = tmf.getTrustManagers();
Use tms to init your SSLContextinstead for the new trust settings to be used for your SSL/TLS connection.
Also you should make sure that the CN part of the server TLS certificate is equal to the FQDN (fully qualified domain name) of your server, e.g. if your server base URL is 'https://www.example.com', then the CN of the certificate should be 'www.example.com'. This is needed for host name verification, a feature that prevents man-in-the-middle-attacks. You could disable this, but only when using this your connection will be really secure.
Create your trust store, store at as an asset and use it initialize this SocketFactory. Then use the factory instead of your own 'trust everybody' one.
If you want to ignore all the certificate, ignore the handshake, then this works:
HttpsURLConnection and intermittent connections
Related
I am working on SSL two way authentication using self signed certificates.
I have created two keystores for client(client-keystore.jks) and server(server-keystore.jks), Exported the certificates from both the keystores and imported client certificate into server keystore and server certificate into client keystore. and updated required connector entry in the server.xml,added both the certificates to the java trust store cacerts.
Java Client code:
KeyStore trustStore = KeyStore.getInstance("JKS", "SUN");
trustStore.load(SSLImplemetation.class.getResourceAsStream("C:/Program Files/Java/jdk1.7.0_79/jre/lib/security/cacerts"), "changeit".toCharArray());
String alg = KeyManagerFactory.getDefaultAlgorithm();
TrustManagerFactory fac = TrustManagerFactory.getInstance(alg);
fac.init(trustStore);
KeyStore keystore = KeyStore.getInstance("JKS", "SUN");
keystore.load(SSLImplemetation.class.getResourceAsStream("<dir path>/client-keystore.jks"), "test".toCharArra());
String keyAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyFac = KeyManagerFactory.getInstance(keyAlg);
keyFac.init(keystore, "test".toCharArray());
SSLContext ctx = SSLContext.getInstance("TLS", "SunJSSE");
ctx.init(keyFac.getKeyManagers(),fac.getTrustManagers(), new SecureRandom());
SslContextedSecureProtocolSocketFactory secureProtocolSocketFactory = new SslContextedSecureProtocolSocketFactory(ctx);
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) secureProtocolSocketFactory, 8443));
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost request = new HttpPost("<rest service url>");
JSONObject obj = new JSONObject();
StringEntity params =new StringEntity(obj.toString());
request.addHeader("content-type", "application/json");
request.setEntity(params);
HttpResponse response = httpClient.execute(request);
System.out.println(response.getStatusLine());
server.xml:
<Connector
protocol="org.apache.coyote.http11.Http11Protocol"
port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="<dir path>/server-keystore.jks" keystorePass="test"
truststoreFile="C:/Program Files/Java/jdk1.7.0_79/jre/lib/security/cacerts"
truststorePass="changeit" />
I am new to SSl,so bit confused.
Any help would be appreciated.
Below code works fine for me, after creating two client and server keystore and place both the certificate into java truststore, I have used below code for SSL Mutual Authentication.
static final private String KEY_STORE = "D:/sslcertificates/client-keystore.jks";
static final private String KEY_STORE_TYPE = "JKS";
static final private String KEY_STORE_PASS = "test";
public static void main(String[] args) throws JSONException{
try {
URL url = new URL("<rest service url>");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("<hostname>")) {
return true;
}
return false;
}
});
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
try
{
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509","SunJSSE");
KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE);
File cert = new File(KEY_STORE);
InputStream stream = new FileInputStream(cert);
ks.load(stream, KEY_STORE_PASS.toCharArray());
stream.close();
kmf.init(ks,KEY_STORE_PASS.toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(),null, new SecureRandom());
SSLSocketFactory factory = context.getSocketFactory();
conn.setSSLSocketFactory(factory);
}
catch(Exception e)
{
System.out.println(e);
}
JSONObject obj = new JSONObject();
obj.put("id","abc");
obj.put("name","surya");
obj.put( "domain","IT");
String input=obj.toString();
//send request to server....
OutputStream os = conn.getOutputStream();
os.write(input.getBytes());
os.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
System.out.println("Output from Server .... \n");
while ((output = br.readLine()) != null) {
System.out.println(output);
}
System.out.println("Response code: "+conn.getResponseCode());
System.out.println("Response message: "+conn.getResponseMessage());
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Server.xml
<Connector
protocol="org.apache.coyote.http11.Http11Protocol"
port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="<dir path>/server-keystore.jks" keystorePass="test"/>
NOTE: Set HostnameVerifier in HttpsURLConnection object and CN of client and server keystore should be host name.
Please suggest, if any better solution.
I have an app that serves to consume and update data to a webserver and, recently, the app owner decided to switch to a secure connection due to personal information stored.
The server is already set up as SNI and I have checked it using digicert, the server is working fine and seems to be set up correctly, but does not include the path *.host.com on its alternate names (I am unsure if this is normal or not for SNI).
The iOS worked like a charm, however on Android I get this error:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
My current connection method looks like this:
URL url = new URL(postURL);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
SSLContext sc;
sc = SSLContext.getInstance("TLS");
sc.init(null, null, new java.security.SecureRandom());
conn.setSSLSocketFactory(sc.getSocketFactory());
String userpass = "bob" + ":" + "12345678";
String basicAuth = "Basic " + Base64.encodeToString(userpass.getBytes(), Base64.DEFAULT);
conn.setRequestProperty("Authorization", basicAuth);
conn.setReadTimeout(7000);
conn.setConnectTimeout(7000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.connect();
InputStream instream = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
StringBuilder everything = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
everything.append(line);
}
JSONObject jsonObject = new JSONObject(everything.toString());
return jsonObject;
I'm not quite sure what's the issue here, but trying to connect to https://sni.velox.ch/ gives me a long answer that seems like a success.
Also, I do have the pem key for the certificate here with me, but I do not know how I add that in this context.
Usually you get this error when using a self-signed certificate, in which case you would have to use the certificate while making the request.
Additionally, you might be getting this error because of not including the path *.host.com.
You could try the below code to pass your certificate while building the HttpsURLConnection. Please don't forget to copy the ca.pem file to assets folder.
private HttpsURLConnection buildSslServerConnection() {
HttpsURLConnection urlConnection = null;
try {
// Load CAs from an InputStream
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(context.getAssets().open("ca.pem"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Authorization", "Basic" + Base64.encodeToString(userpass.getBytes(), Base64.DEFAULT));
urlConnection.setConnectTimeout(7000);
urlConnection.setReadTimeout(7000);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setUseCaches(false);
urlConnection.setAllowUserInteraction(false);
urlConnection.setDoOutput(false);
} catch (KeyManagementException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (CertificateException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (FileNotFoundException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (KeyStoreException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (NoSuchAlgorithmException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (MalformedURLException e) {
LOG.error("Error while checking server connectivity: ", e);
} catch (IOException e) {
LOG.error("Error while checking server connectivity: ", e);
}
return urlConnection;
}
Hope this helps.
I am making a financial transaction android app. It requires SSL authentication and I successfully able to complete it(handshake between Android and Tomcat). I used keytool and openSSL to generate Server and client certificates. Tomcat certifcate format is JKS and android formate is BKS. I stored this BKS file in Raw folder and use this as follows:
public class NetworkCallSecure extends AsyncTask<String, Void, String> {
ResponseListener responseListener;
Activity activity;
ResultCodes code;
public NetworkCallSecure(Activity activity, ResponseListener responseListener, ResultCodes code) {
this.responseListener = responseListener;
this.activity = activity;
this.code = code;
}
#Override
protected String doInBackground(String... params) {
try{
System.setProperty("http.keepAlive", "false");
HttpsURLConnection .setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname,
SSLSession session) {
Log.d("HTTPS",hostname+":"+session);
return true;
}
});
char[] passwKey = "mypass".toCharArray();
KeyStore ks = KeyStore.getInstance("BKS");
InputStream in = activity.getResources().openRawResource(
R.raw.client);
InputStream is = activity.getResources().openRawResource(
R.raw.client);
ks.load(in, passwKey);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ks, passwKey);
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(),
new X509TrustManager[] { new MyX509TrustManager(is,
passwKey) }, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
URL url = new URL(params[0]);
HttpsURLConnection connection = (HttpsURLConnection) url
.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Length", "" + Integer.toString(params[1].getBytes().length));
connection.setDoOutput(true);
byte[] outputInBytes = params[1].getBytes("UTF-8");
OutputStream os = connection.getOutputStream();
os.write( outputInBytes );
os.close();
BufferedReader bin = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = bin.readLine()) != null) {
sb.append(line);
}
in.close();
is.close();
return sb.toString();
} catch (Exception e) { // should never happen
e.printStackTrace();
Log.d("Err", e.toString());
}
return "no result";
}
#Override
protected void onPostExecute(String result) {
responseListener.getResponse(result,code);
}
}
My Trustmanager class is:
public class MyX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
public MyX509TrustManager(InputStream trustStore, char[] password)
throws Exception {
// create a "default" JSSE X509TrustManager.
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(trustStore, password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager tms[] = tmf.getTrustManagers();
/*
* Iterate over the returned trustmanagers, look for an instance of
* X509TrustManager. If found, use that as our "default" trust manager.
*/
for (int i = 0; i < tms.length; i++) {
if (tms[i] instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) tms[i];
return;
}
}
/*
* Find some other way to initialize, or else we have to fail the
* constructor.
*/
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
try {
pkixTrustManager.checkClientTrusted(arg0, arg1);
} catch (CertificateException excep) {
// do any special handling here, or rethrow exception.
}
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
try {
pkixTrustManager.checkServerTrusted(arg0, arg1);
} catch (CertificateException excep) {
/*
* Possibly pop up a dialog box asking whether to trust the cert
* chain.
*/
}
}
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return pkixTrustManager.getAcceptedIssuers();
}
}
Now I want to register user using this HTTPS connection. The process is get details from user and send it to server. Server will verify these details and send confirmation PIN on user mobile (got this MSISDN in user details). User will enter this PIN and server will verify that PIN is same. After user is verified client app (user mobile) will generate a CSR and send it to server. Server will generate Certificate using this CSRand send it to client (mobile app).
Now my problem is I want to store this certificate where only my App can access this certificate. I am trying to save this in my BKS file in raw folder using this:
private boolean storeCertInKeystore(byte[] cert) {
try {
InputStream is = getResources().openRawResource(
R.raw.client);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream certstream = new ByteArrayInputStream(cert);
X509Certificate certificate = (X509Certificate) cf.generateCertificate(certstream);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(is, "mypass".toCharArray());
keyStore.setCertificateEntry("mycert", certificate);
Log.d("My App Cert: ", "true");
return true;
} catch(Exception e) {
e.printStackTrace();
}
return false;
}
This code runs successfully but could not store cert in BKS file. I tried another way describe here but could not succeed. (I want to use this certificate later in my app for client authentication)
My Question is Q. How can I store this certificate so it can be only accessible by my app? And also I can delete this certificate when user registration expires.
Please help and thanks in advance.
Your issue is not with the keystore itself, but rather with the
location of the file where you're trying to store the new client
certificate!
The "RAW-folder" is part of your installed application package. So
you can "virtually" access it, and only READ, not WRITE!
Your best option, if you want your keystore to be private, is your
application sandboxed-private-folder(Internal storage).
You can't write in the RAW-folder, but you can write in your application private folder.
In the link that you provided, the storage/write location is in
fact the private folder. So it didn't work for you because you're
trying to "write in the Raw-Folder"
You probably know that already, but you can copy your file(R.raw.client)
from the "Raw-folder" to your application private folder. That way, you only use one keystore file(readable and writable).
EDITTED: "The code below works fine, no errors, no exceptions"
I'm aware of the grand amount of questions in regards to this topic, as well as the many blogs that google conjures up. I have read through them and have managed to come up with what I'm about to explain. My doubt lies in "is my approach correct? Does it have any side-effects?" and another question that is better asked as I explain my method.
I based this approach following this Android.Developres tutorial.
System.setProperty("jsse.enableSNIExtension", "false");
//Java 7 introduced SNI (enabled by default). The server I use is
// misconfigured I suppose and
// it sends an "Unrecognized Name" warning in the SSL handshake
// which breaks my web service.
// Load CA from an InputStream (CA would be saved in Raw file,
// and loaded as a raw resource)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(new FileInputStream("PATH_TO_CERT.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Create all-trusting host name verifier
// to avoid the following :
// java.security.cert.CertificateException: No name matching
// This is because Java by default verifies that the certificate CN (Common Name) is
// the same as host name in the URL. If they are not, the web service client fails.
HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
};
//Install it
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https....");
urlConnection.setSSLSocketFactory(context.getSocketFactory());
try {
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
switch(urlConnection.getResponseCode()){
case 401:
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getErrorStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line+"\n");
}
br.close();
System.out.println( sb.toString());
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Here is my other question, in the following line:
InputStream caInput = new BufferedInputStream(new FileInputStream("PATH_TO_CERT.crt"));
You see that the method forces me to have the certificate.crt preloaded onto raw file inside res folder. Is there a way (I have looked but have found 0 answers) to connect to the server and download said certificate.crt and save it on a private folder not accessible by the user?
I want to open a secure listening socket in a Java server application. I know that the recommended way to do that is to just do this:
SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
ServerSocket ss = ssf.createServerSocket(443);
But this requires to pass the certificate of the server to the JVM when launching java. Because this would make some things in deployment more complicated for me, I would prefer to load the certificate at runtime.
So I have a key file and a password and I want a server socket. How do I get there? Well, I read the documentation and the only way I could find is this:
// these are my parameters for SSL encryption
char[] keyPassword = "P#ssw0rd!".toCharArray();
FileInputStream keyFile = new FileInputStream("ssl.key");
// init keystore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyFile, keyPassword);
// init KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword);
// init KeyManager
KeyManager keyManagers[] = keyManagerFactory.getKeyManagers();
// init the SSL context
SSLContext sslContext = SSLContext.getDefault();
sslContext.init(keyManagers, null, new SecureRandom());
// get the socket factory
SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
// and finally, get the socket
ServerSocket serverSocket = socketFactory.createServerSocket(443);
And that doesn't even have any error handling. Is it really that complicated? Isn't there an easier way to do it?
But this requires to pass the certificate of the server to the JVM when launching java.
No it doesn't. Just set these system properties before you create the SSLServerSocket:
javax.net.ssl.keyStore ssl.key
javax.net.ssl.keyStorePassword P#ssw0rd!
You can do that with System.setProperties() or on the command line.
If you look at the code, you can see why it's necessarily complicated. This code decouples the implementation of the SSL protocol from:
the source of your key material (KeyStore)
certificate algorithm choice and key management (KeyManager)
management of peer trust rules (TrustManager) - not used here
secure random algorithm (SecureRandom)
NIO or socket implementation (SSLServerSocketFactory) - could use SSLEngine for NIO
Consider what your own implementation would look like if you were trying to reach the same goals!
Use this.
public class KeyMaster
{
public static SSLSocketFactory getSSLSocketFactory(KeyStore trustKey, String sslAlgorithm)
{
try
{
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKey);
SSLContext context = SSLContext.getInstance(sslAlgorithm);//"SSL" "TLS"
context.init(null, tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
catch(Exception e)
{
Assistance.log("Err: getSSLSocketFactory(), ");
}
return null;
}
public static SSLServerSocketFactory getSSLServerSocketFactory(KeyStore trustKey, String sslAlgorithm)
{
try
{
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKey);
SSLContext context = SSLContext.getInstance(sslAlgorithm);//"SSL" "TLS"
context.init(null, tmf.getTrustManagers(), null);
return context.getServerSocketFactory();
}
catch(Exception e)
{
Assistance.log("Err: getSSLSocketFactory(), ");
}
return null;
}
public static SSLServerSocket getSSLServerSocket(SSLServerSocketFactory socketFactory, int port)
{
try
{
return (SSLServerSocket) socketFactory.createServerSocket(port);
}
catch(Exception e)
{Assistance.log("Err: getSSLSocket(), ");}
return null;
}
public static KeyStore getFromPath(String path, String algorithm, String filePassword)//PKSC12
{
try
{
File f = new File(path);
if(!f.exists())
throw new RuntimeException("Err: File not found.");
FileInputStream keyFile = new FileInputStream(f);
KeyStore keystore = KeyStore.getInstance(algorithm);
keystore.load(keyFile, filePassword.toCharArray());
keyFile.close();
return keystore;
}
catch(Exception e)
{
Assistance.log("Err: getFromPath(), " + e.toString());
}
return null;
}
and in main.
KeyStore key = KeyMaster.getFromPath(".\\cssl.pfx", "PKCS12", "123");
SSLServerSocketFactory fac = KeyMaster.getSSLServerSocketFactory(key, "TLS");
listener = KeyMaster.getSSLServerSocket(fac, 49015);