using X509Certificate from KeyChain in OKHttpClient sslSocketFactory - java

The bounty expires in 7 days. Answers to this question are eligible for a +500 reputation bounty.
Mr Heelis wants to draw more attention to this question.
I'm an Android Developer who has to use KeyChain not KeyStore. The KeyStore variant of our code works. I need to add KeyChain equivalent.
this works
InputStream inputStream = RN.getReactContext().getResources().getAssets().open("xxxx-xxxxx-xxxxx-xxxx.pfx");
keyStore = KeyStore.getInstance("PKCS12");
KeyStore.LoadStoreParameter
keyStore.load(inputStream,PASSWORD);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance (TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager))
{
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
trustManager = trustManagers;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore,PASSWORD);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15000, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(15000, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManager[0]);
OkHttpClient okHttpClient = builder.build();
The problem is this line InputStream inputStream = RN.getReactContext().getResources().getAssets().open("xxxx-xxxxx-xxxxx-xxxx.pfx"); we're not allowed to use the assets folder (for reasons outside the scope of this conversation) but we are allowed to put the self same file in the KeyChain so I did, and I can retrieve it using the following. X509Certificate[] chain = KeyChain.getCertificateChain(RN.getReactContext(), "xxxx-xxxxx-xxxxx-xxxx");
so since
X509Certificate[] chain = KeyChain.getCertificateChain(RN.getReactContext(), "xxxx-xxxxx-xxxxx-xxxx"); //this gets the correct X509Certificate
Gets the certificate via KeyChain my instinct was to swap it out with this:
X509TrustManager customTm = new X509TrustManager() {
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
try {
return X509Certificate[] chain = KeyChain.getCertificateChain(RN.getReactContext(), "xxxx-xxxxx-xxxxx-xxxx");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeyChainException e) {
e.printStackTrace();
}
return null;
}
};
TrustManager[] trustManager = new TrustManager[] { customTm };
sslContext.init(null, trustManager, null);
but it doesn't work, so my question is: How do I use the X509Certificate I have from the KeyChain as a drop in replacement to the asset I pulled into the KeyStore?

Related

AWS MQTT Websocket issue

I am trying use AWS connection keys in Android for connection with Device using MQTT websocket but did not find a solution yet, please help me on that. If any one provide MQTT over SSL/TLS with 3 certificate AWS (ca,cert,private) android code.
final MqttAndroidClient mqttAndroidClient = new MqttAndroidClient(MainActivity.this, "ssl://" + pref.getMqttUrl(), clientId, persistence);
try {
String clientId = MqttClient.generateClientId();
MqttConnectOptions connectionOptions = new MqttConnectOptions();
connectionOptions.setCleanSession(true);
Log.e("Test", "ssl://" + pref.getMqttUrl());
try {
InputStream trustStoresIs = context.getResources().openRawResource(R.raw.ca_key);
String trustStoreType = KeyStore.getDefaultType();
KeyStore trustStore = KeyStore.getInstance(trustStoreType);
trustStore.load(trustStoresIs, context.getString(R.string.bks_password).toCharArray());
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trustStore);
InputStream keyStoreStream = context.getResources().openRawResource(R.raw.user_cer_key);
KeyStore keyStore = null;
keyStore = KeyStore.getInstance("BKS");
keyStore.load(keyStoreStream, context.getString(R.string.bks_password).toCharArray());
KeyManagerFactory keyManagerFactory = null;
keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, context.getString(R.string.bks_password).toCharArray());
SSLContext context = SSLContext.getInstance("SSL");
context.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) context.getSocketFactory();
connectionOptions.setSocketFactory(sslsocketfactory);
} catch (KeyManagementException | CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mqttAndroidClient.connect(connectionOptions, null, new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.e("Mqtt","Connection Success!");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e("Mqtt","Connection Failure!");
}
});
mqttAndroidClient.setCallback(new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
Log.e("Mqtt","Connection was lost!");
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("mqtt Delivery Complete!");
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
Got error like
Mqttjavax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Thanks.
The error implies your truststore doesn't have either the right or the full certificate chain for to authenticate the server.
The following line bothers me:
InputStream trustStoresIs =
context.getResources().openRawResource(R.raw.ca_key);
It implys you are loading a key not a certificate for your CA chain. Also CA chains tend to have more than one layer (Primary CA cert signs Intermediate CA cert) so I would expect you to need to add more than one cert to the trust store.

Using .cer certificate to make an HTTPS request

I've already saw this question: Need to do a GET&POST HTTPS Request using a .cer certificate
Mine is quite different:
It is possible to make an HTTPS request using Java (vanilla, or using any library), trusting a server certificate and providing a client certificate, without using a keystore but using plain certificates?
I have both certs in X.509 format, and I don't want to have every certificate in a keystore.
This is a rough example. Represents the X509KeyManager decorator.
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(null, null);
X509KeyManager manager = (X509KeyManager) kmf.getKeyManagers()[0];
KeyManager km = new X509KeyManager() {
#Override
public String[] getClientAliases(String s, Principal[] principals) {
return manager.getServerAliases(s, principals);
}
#Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
return manager.chooseClientAlias(strings, principals, socket);
}
#Override
public String[] getServerAliases(String s, Principal[] principals) {
return manager.getServerAliases(s, principals);
}
#Override
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
return manager.chooseServerAlias(s, principals, socket);
}
#Override
public X509Certificate[] getCertificateChain(String s) {
// You can use `s` to select the appropriate file
try {
File file = new File("path to certificate");
try(InputStream is = new FileInputStream(file)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return new X509Certificate[] {
(X509Certificate) factory.generateCertificate(is)
};
}
}
catch (CertificateException| IOException e) {
e.printStackTrace();
}
return null;
}
#Override
public PrivateKey getPrivateKey(String s) {
// You can use `s` to select the appropriate file
// load and private key from selected certificate
// this use for certificate authorisation
try {
File file = new File("private key file");
byte buffer[] = Files.readAllBytes(file.toPath());
KeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(keySpec);
}
catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
};
TrustManager tm = new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
try {
File file = new File("path to certificate");
try(InputStream is = new FileInputStream(file)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return new X509Certificate[] {
(X509Certificate) factory.generateCertificate(is)
};
}
}
catch (CertificateException| IOException e) {
e.printStackTrace();
}
return null;
}
};
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore)null); //use java system trust certificates
TrustManager managers[] = new TrustManager[tmf.getTrustManagers().length + 1];
System.arraycopy(tmf.getTrustManagers(), 0, managers, 0, tmf.getTrustManagers().length);
managers[managers.length - 1] = tm;
SSLContext context = SSLContext.getInstance("TLS");
context.init(new KeyManager[]{ km }, managers, new SecureRandom());
URL url = new URL("https://............/");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(connection.getSSLSocketFactory());
connection.connect();
If you really don't want to create a new keystore file, then can use KeyStore API to create in memory and load certificate directly.
InputStream is = new FileInputStream("somecert.cer");
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
Alternatively, if you want to avoid modifying your default cacerts file, then you'll need to implement your own TrustManager. However a TrustManager needs a keystore to load, so you can either create a new keystore file importing just your certificate.
keytool -import -alias ca -file somecert.cer -keystore truststore.jks -storepass changeit
And use something like following snippet to load the keystore file.
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);
// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
defaultTm = (X509TrustManager) tm;
break;
}
}
FileInputStream myKeys = new FileInputStream("truststore.jks");
// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());
myKeys.close();
tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

wss web socket creation issue

I got - closed connection -1 , draft org.java_websocket.drafts.Draft_17#75599672 refuses handshake , false - then changed the code as below:
Socket creation coode
webClient = new APICWebClient(new URI(getWebSocketUrl(currentApic.IPAddress, port)), new Draft_17());
webClient.connect();
Constructor:
public APICWebClient(URI serverURI, Draft draft) {
super(serverURI, draft);
SSLContext sslContext = null;
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sslContext));
logger.info("Socket object created");
}
Draft_17. I get : closed connection -1 , , true. Any help here. This happens during socket creation.
Need to create sslcontext like below. it skips the certificate. I was successfully able make a connection without certificate
SSLContext sslContext = SSLContext.getInstance("SSL");
// set up a TrustManager that trusts everything
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
System.out.println("getAcceptedIssuers =============");
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkClientTrusted =============");
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkServerTrusted =============");
}
} }, new SecureRandom());

Programmatically add a certificate authority while keeping Android system SSL certificates

There are lots of questions about this topic on StackOverflow, but I do not seem to find one related to my problem.
I have an Android application that needs to communicate with HTTPS servers: some signed with a CA registered in the Android system keystore (common HTTPS websites), and some signed with a CA I own but not in the Android system keystore (a server with an autosigned certificate for instance).
I know how to add my CA programmatically and force every HTTPS connection to use it. I use the following code:
public class SslCertificateAuthority {
public static void addCertificateAuthority(InputStream inputStream) {
try {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(inputStream);
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
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
However, doing that disables the use of the Android system keystore, and I cannot query HTTPS sites signed with other CA any more.
I tried to add my CA in the Android keystore, using:
KeyStore.getInstance("AndroidCAStore")
... but I cannot then add my CA in it (an exception is launched).
I could use the instance method HttpsURLConnection.setSSLSocketFactory(...) instead of the static global HttpsURLConnection.setDefaultSSLSocketFactory(...) to tell on a case by case basis when my CA has to be used.
But it isn't practical at all, all the more since sometimes I cannot pass a preconfigured HttpsURLConnection object to some libraries.
Some ideas about how I could do that?
EDIT - ANSWER
Ok, following the given advice, here is my working code. It might need some enhancements, but it seems to work as a starting point.
public class SslCertificateAuthority {
private static class UnifiedTrustManager implements X509TrustManager {
private X509TrustManager defaultTrustManager;
private X509TrustManager localTrustManager;
public UnifiedTrustManager(KeyStore localKeyStore) throws KeyStoreException {
try {
this.defaultTrustManager = createTrustManager(null);
this.localTrustManager = createTrustManager(localKeyStore);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
private X509TrustManager createTrustManager(KeyStore store) throws NoSuchAlgorithmException, KeyStoreException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init((KeyStore) store);
TrustManager[] trustManagers = tmf.getTrustManagers();
return (X509TrustManager) trustManagers[0];
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
localTrustManager.checkServerTrusted(chain, authType);
}
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkClientTrusted(chain, authType);
} catch (CertificateException ce) {
localTrustManager.checkClientTrusted(chain, authType);
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] first = defaultTrustManager.getAcceptedIssuers();
X509Certificate[] second = localTrustManager.getAcceptedIssuers();
X509Certificate[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
}
public static void setCustomCertificateAuthority(InputStream inputStream) {
try {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(inputStream);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} 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 and system CA
UnifiedTrustManager trustManager = new UnifiedTrustManager(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{trustManager}, null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
It is an old question, but I met the same problem, so probably it is worth posting my answer. You tried to add your certificate to KeyStore.getInstance("AndroidCAStore"), but got an exception. Actually you should have done the opposite - add entries from that keystore to the one you created.
My code is a bit different from yours, I just post it for the sake of complete answer even though only middle part matters.
KeyStore keyStore=KeyStore.getInstance("BKS");
InputStream in=activity.getResources().openRawResource(R.raw.my_ca);
try
{
keyStore.load(in,"PASSWORD_HERE".toCharArray());
}
finally
{
in.close();
}
KeyStore defaultCAs=KeyStore.getInstance("AndroidCAStore");
if(defaultCAs!=null)
{
defaultCAs.load(null,null);
Enumeration<String> keyAliases=defaultCAs.aliases();
while(keyAliases.hasMoreElements())
{
String alias=keyAliases.nextElement();
Certificate cert=defaultCAs.getCertificate(alias);
try
{
if(!keyStore.containsAlias(alias))
keyStore.setCertificateEntry(alias,cert);
}
catch(Exception e)
{
System.out.println("Error adding "+e);
}
}
}
TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// Get a new SSL context
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null,tmf.getTrustManagers(),new java.security.SecureRandom());
return ctx.getSocketFactory();
This might be too late but this is a tried and tested approach that helps bypass the certificate check done by Java.
I cannot claim credit for this code, it was written by one of my colleagues :) . It can be used during development to test your code. In case you don't want to deal with certificates at all, you can make Java always certificates from any host for your HttpURLConnection object. Which seems to be exactly what you're trying to do here.
Here's a class that should help you do that :
import javax.net.ssl.*;
import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/***
* Should only be used in development, this class will allow connections to an HTTPS server with unverified certificates.
* obviously this should not be used in the real world
*/
public class TrustModifier {
private static final TrustingHostnameVerifier TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier();
private static SSLSocketFactory factory;
/**
* Call this with any HttpURLConnection, and it will modify the trust settings if it is an HTTPS connection.
*
* #param conn the {#link HttpURLConnection} instance
* #throws KeyManagementException if an error occurs while initializing the context object for the TLS protocol
* #throws NoSuchAlgorithmException if no Provider supports a TrustManagerFactorySpi implementation for the TLS protocol.
*/
public static void relaxHostChecking(HttpURLConnection conn) throws KeyManagementException, NoSuchAlgorithmException {
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) conn;
SSLSocketFactory factory = prepFactory();
httpsConnection.setSSLSocketFactory(factory);
httpsConnection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER);
}
}
/**
* Returns an {#link SSLSocketFactory} instance for the protocol being passed, this represents a secure communication context
*
* #return a {#link SSLSocketFactory} object for the TLS protocol
* #throws NoSuchAlgorithmException if no Provider supports a TrustManagerFactorySpi implementation for the specified protocol.
* #throws KeyManagementException if an error occurs while initializing the context object
*/
static synchronized SSLSocketFactory prepFactory() throws NoSuchAlgorithmException, KeyManagementException {
if (factory == null) {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new AlwaysTrustManager()}, null);
factory = ctx.getSocketFactory();
}
return factory;
}
private static final class TrustingHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
private static class AlwaysTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
All you need to do is call the function relaxHostChecking() like this :
if (conn instanceof HttpsURLConnection) {
TrustModifier.relaxHostChecking(conn);
}
This will result in java trusting whichever host you're trying to connect to using HttpURLConnection.

Java: Loading SSL Keystore via a resource

If I have:
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/fdms/WS1001237590._.1.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'DV8u4xRVDq');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
I'm able to open a secure connection without a problem.
However, I'd like to have the certificates stored directly in the war, so I use: (The file input stream will eventually become a resource stream, but I'm doing this to get it to work.)
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/etc/certificates/fdms/WS1001237590._.1.ks"), "DV8u4xRVDq".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "DV8u4xRVDq".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
Now, if I open the same connection, I get: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
I had to do something similar a while back. I had a certificate file and I had to figure out a way to load it in and use it for an SSL connection. Hopefully what I did will help you out.
First I had to create a trust manager:
public class MyX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
MyX509TrustManager() throws Exception {
String certFile = "/certificates/MyCertFile.cer";
Certificate myCert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("myCert", myCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
After that I had to create a socket factory that used my trust manager:
public class MySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext sslContext = null;
public MySSLProtocolSocketFactory() {
super();
}
private static SSLContext createMySSLContext() {
try {
MyX509TrustManager myX509TrustManager = new MyX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new MyX509TrustManager[] { myX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createMySSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(MySSLProtocolSocketFactory.class));
}
public int hashCode() {
return MySSLProtocolSocketFactory.class.hashCode();
}
}
Then I used that socket factory to send my POST:
Protocol.registerProtocol("myhttps", new Protocol("myhttps", new MySSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod("myhttps://some.url.here");
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
The only thing I couldn't figure out was how to simply add the certificate file to the regular keystore. All the example source code I found during my research pointed to creating a socket factor and then registering a protocol with that socket factory. Perhaps there is a way to simply use the socket factory to make a connection without registering a protocol; I haven't investigated that thoroughly. In my particular situation, creating a specific protocol was necessary. Hopefully this will get your further along the way. I admit it seems a bit roundabout; I felt the same way when I did it initially. But this was the only way I got it to work. Maybe other people have a better solution.
For posterity's sake, all of this was far too complicated, and we pretty much just had a check in the static block:
if( environment == 'production') {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/prod/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
} else {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/test/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
}
With Axis, I think you need to configure its SSLSocketFactory via:
AxisProperties.setProperty("axis.socketSecureFactory",
"com.example.MySSLSocketFactory");
where com.example.MySSLSocketFactory is your class that implements org.apache.axis.components.net.SecureSocketFactory (you could extend org.apache.axis.components.net.JSSESocketFactory perhaps).
In the create method, create a socket using the socket factory obtained from the SSLContext you've configured.
If you want, here's an API to create SSLSocket and SSLServerSocket easily:
https://github.com/gpotter2/SSLKeystoreFactories
It does not require any other jars.... just get the files and use them like:
SSLSocket s = SSLSocketKeystoreFactory.getSocketWithCert(ip, port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
Or:
SSLServerSocket s = SSLServerSocketKeystoreFactory.getSocketWithCert(port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
That's much easier to use :)
I had similar problem, I solved creating a function that returns an SSL context using a keystore coming from and input stream.
protected SSLContext getSslCtx(InputStream is, String password) {
try {
// Load keystore
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(is, password.toCharArray());
// Load trust manager
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(keystore);
// Load key manager
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyMgrFactory.init(keystore, password.toCharArray());
// Create SSL context
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(keyMgrFactory.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);
return ctx;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Hope this helps.

Categories