I'm building a client for a SOAP webservice that is controlled by a vendor. Unfortunately their dev server has an insecure (self-signed) certificate that I'm unable to validate. Apache Axis fails each time I attempt to make a request. Is there a way to ignore SSL validation errors? I obviously don't want to do this for production but it would be good in my dev environment.
After trying a number of solutions, what ultimately worked was installing a custom protocol handler and associating it with the particular ServiceClient:
private void configureServiceClient(ServiceClient client) {
SSLContext ctx;
try {
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(getClass().getResourceAsStream("/truststore.jks"),
"latitude".toCharArray());
ctx = SSLContext.getInstance("SSL");
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
ctx.init(null, tmf.getTrustManagers(), null);
} catch (Exception e) {
logger.error("Exception loading Bold trust store", e);
throw new RuntimeException(e);
}
SSLProtocolSocketFactory sslFactory = new SSLProtocolSocketFactory(ctx);
Protocol prot = new Protocol("https",
(ProtocolSocketFactory) sslFactory, 443);
client.getOptions().setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER,
prot);
}
The advantage to this was it didn't require me to override all of the SSL connections for my application which could have potentially broken things down the road if new certificates were issued. This one will definitely break if a new cert is issued, but it's only one connection rather than all of them.
Related
I am trying to implement secured Socket connections between a Spring Server and an Android Client but I found some design problems.
At the beginning I implemented the solution provided in this tutorial (it's in Spanish but easily understandable with Google Traductor). This tutorial secures Socket connections with a key in each side (one for server and one for client) and uses a trustedKeys.jks to store trusted keys in each side.
That means that for every client I should:
Create a new keystore for each new client
Add this new client key to trusted keys on server side
Add server keystore to each new client
This seemed unrealistic to me considering the growth of the number of clients.
I found another approach that suits better my requirements, it uses the server certificate as a public key (I think) and ciphers the data with it, that will be deciphered by the server keystore:
Server Code
public SSLServerSocket getSSLServerSocket(int port) throws IOException, ...
{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(resourceKeyFile.getInputStream(), keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, keystorePassword.toCharArray());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketfactory = sc.getServerSocketFactory();
return (SSLServerSocket) sslServerSocketfactory.createServerSocket(port);
}
Here is my function for creating a SSLServerSocket. In the KeyManagerFactory initialization kmf.init(keyStore, keystorePassword.toCharArray()); I set the server keystore keystore with its password keystorePassword which are read with #Value Spring Annotation outside the function. This function will return the SSLServerSocket which will accept client connections in a new Thread like this:
while(true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
}
Client code
SSLContext context = socketUtil.createSSLContext();
SSLSocketFactory sf = context.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket(serverUrl, port);
This code creates the context I want to usem which is presented below, and creates the Socket to an address serverUrl and a port port:
public final SSLContext createSSLContext()
throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream in = getClass().getResourceAsStream("serverCert.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("serverKey", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
Here, I load the Server certificate serverCert.pem which I generated from the keystore with the keytool utility. Then other parameters are setted, such as the algorithm or the protocol. Thus, the socket will trust connections from the issuer of the certificate.
The code is working just nice and smooth, but I'm concerned about security and good practices. So once again, is this a good approach to solve my problem?
Any clarifications about the process, code or misunderstandings are welcome. Thank you so much!
I've implemented certificate pinning using stored certificates of this certificate chain:
Certificate 1: Cloud Platform
Certificate 2: Verizon (Intermediate CA)
Certificate 3: Baltimore (Root CA)
I've noticed a strange behavior though:
Behavior 1 (Expected): If I only pinned certificate 1, I'll get an
SSLHandshakeException error because I need to include all
certificates in the chain.
Behavior 2 (Unexpected?): If I only pinned certificate 2 which is the
intermediate CA, I won't get any SSLHandshakeException error at all.
Would you know if behavior 2 is expected and if yes, why? I was under the impression that all certificates in the chain should be used else I'll get an SSLHandshakeException. Thanks!
Updated with Code
class SSLPinning {
void exec() {
// Open InputStreams for each certificate
InputStream baltimoreInputStream = getClass().getResourceAsStream("baltimore.cer");
InputStream hcpmsInputStream = getClass().getResourceAsStream("hcpms_cert.cer");
InputStream verizonInputStream = getClass().getResourceAsStream("verizon.cer");
try {
// CertificateFactory has the method that generates certificates from InputStream
// Default type for getInstance is X.509
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// Create Certificate objects for each certificate
Certificate baltimoreCertificate = cf.generateCertificate(baltimoreInputStream);
Certificate hcpmsCertificate = cf.generateCertificate(hcpmsInputStream);
Certificate verizonCertificate = cf.generateCertificate(verizonInputStream);
// Create KeyStore and load it with our certificates
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
//keyStore.setCertificateEntry("hcpms", hcpmsCertificate);
keyStore.setCertificateEntry("intermediate", verizonCertificate); //surprisingly, it works with just using the intermediate CA
//keyStore.setCertificateEntry("root", baltimoreCertificate);
// Create a TrustManagerFactory using KeyStore -- this is responsible in authenticating the servers
// against our stored certificates
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// Create an SSLContext using TrustManagerFactory -- this will generate the SSLSocketFactory we will use
// during HTTPS connection
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
URL url = new URL("https://account.hanatrial.ondemand.com/");
HttpsURLConnection httpsURLConnection = (HttpsURLConnection)url.openConnection();
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsURLConnection.connect();
System.out.print("Server authentication successful");
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (SSLHandshakeException e) {
System.out.println("Server authentication failed");
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
}
Your main problem is that for verifying a certificate a trusted chain have to be present (a trusted certificate and all intermediate certificates up to the server/leaf-certificate). However most SSL/TLS server do not send the complete chain to you. You sometimes only get the leaf-certificate (no intermediate or root certificate; you can see this e.g. in a Wireshark traffic dump).
Other server may send you the leaf-certificate and all/some intermediate certificates but no root certificate.
In such a case it is crucial that your local truststore contains the missing certificates to build up the complete chain.
By your observation I assume that the server does only send the leaf-certificate without intermediate and root certificate. Therefore for a successful verification your truststore have to include the intermediate certificate as trusted certificate to make it work (otherwise this certificate would be missing). I would recommend to include both the root and the intermediate certificate into your truststore.
BTW: Servers not sending the intermediate CA certificate can also be a server configuration problem. Usually I would recommend to configure a server to send leaf and intermediate certificate.
I have a Java application that connects to another Java app through a socket with SSL, so my client JVM already has the -Djavax.net.ssl.keyStore and -Djavax.net.ssl.trustStore properties set.
This application needs to make some HTTP requests to a web server that requires client authentication. I can open the connection by using a URLConnection in Java which returns an HTTPSURLConnectionImpl.
The client certificate I want to present to the web server in the request is different than the one set as my JVM system property. Is there a way I can set a client cert. as a request property in the HTTPSURLConnectionImpl ?
Setting a SSL "client certificate" is not adequate directly through HTTPSURLConnectionImpl's request properties, because a digital signature is also required to prove you own the certificate. SSL already does all that automatically, so to makes sense to use that layer.
You have two ways to solve your issue going forward.
Through configuration
You can add you client key and certificate to your JVM KeyStore, it should be picked up at Runtime when the server asks for your client-side SSL authentication. (SSL/TLS is designed for that : the server will ask for a client certificate that is signed by it's trusted authority, which allows the SSL Engine to choose the right certificate, even when your KeyStore holds many).
Through Code
You can roll you own SSLContext using custom made KeyStore/TrustStores.
This is a bit complex (I won't elaborate on how to build Keystore instances in Java), but the gist of it is here :
public static void main(String[] args) throws Exception {
KeyStore clientKeyStore = ... // Whatever
KeyStore clientTrustStore = ... // Whatever you need to load
// We build the KeyManager (SSL client credentials we can send)
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(clientKeyStore, "password".toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
// We build the TrustManager (Server certificates we trust)
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(clientTrustStore);
TrustManager[] tm = trustFactory.getTrustManagers();
// We build a SSLContext with both our trust/key managers
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(km, tm, null);
SSLSocketFactory sslSf = sslContext.getSocketFactory();
// We prepare a URLConnection
URL url = new URL("https://www.google.com");
URLConnection urlConnection = url.openConnection();
// Before actually opening the sockets, we affect the SSLSocketFactory
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(sslSf);
// Ready to go !
}
I'm trying to establish a secure connection between two Java projects but I'm getting a SSLHandshakeException (no cipher suites in common). This are the methods to create sockets in both sides:
Client:
private SSLSocket getSocketConnection() throws SSLConnectionException {
try {
/* Load properties */
String keystore = properties.getProperty("controller.keystore");
String passphrase = properties.getProperty("controller.passphrase");
String host = properties.getProperty("controller.host");
int port = Integer.parseInt(properties
.getProperty("controller.port"));
/* Create keystore */
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());
/* Get factory for the given keystore */
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory factory = ctx.getSocketFactory();
return (SSLSocket) factory.createSocket(host, port);
} catch (Exception e) {
throw new SSLConnectionException(
"Problem connecting with remote controller: "
+ e.getMessage(), e.getCause());
}
}
Server:
private SSLServerSocket getServerSocket() throws SSLConnectionException {
try {
/* Load properties */
Properties properties = getProperties("controller.properties");
String keystore = properties.getProperty("controller.keystore");
String passphrase = properties.getProperty("controller.passphrase");
int port = Integer.parseInt(properties
.getProperty("controller.port"));
/* Create keystore */
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());
/* Get factory for the given keystore */
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null, tmf.getTrustManagers(), null);
SSLServerSocketFactory factory = ctx.getServerSocketFactory();
return (SSLServerSocket) factory.createServerSocket(port);
} catch (Exception e) {
throw new SSLConnectionException(
"Problem starting auth server: "
+ e.getMessage(), e.getCause());
}
}
I have a RSA key generated with keytool. This code load it from disk.
What I'm doing wrong?
UPDATE:
I added the a call to setEnabledCipherSuites in both sides with this array:
String enableThese[] =
{
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
};
I get the same result.
On the server side, you're not initialising the keystore/keymanagers, only the truststore/trustmanagers: ctx.init(null, tmf.getTrustManagers(), null).
On the server, initialising the keymanager is always necessary to configure the server certificate. Initialising the truststore is only necessary when you want to use client-certificate authentication. (There are more details in this question for the difference between keymanager and trustmanager.)
Without any keymanager configured, there is no RSA or DSA based certificate available, so no cipher suite that rely on a certificate for authentication (all the ones enabled by default are) are available. Hence, you get no cipher suites in common between the client and the server.
You'd need something like this:
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, password.toCharArray()); // That's the key's password, if different.
// ...
ctx.init(kmf.getKeyManagers(), null, null);
It's not clear from your example, but you shouldn't of course use the same keystore on the client (as a truststore) and on the server side (as a keystore): the private key should only be known to the server, and doesn't need to be in the client's trust store.
EDIT: (I'll try to re-explain in a different way, since it wasn't clear for everyone. Perhaps it might help.)
The following code initialises the SSLContext with a null array of key managers (the first argument): ctx.init(null, tmf.getTrustManagers(), null). (There is no default key manager.)
The key manager is what manages your (private) keys and certificates, on the side where the code is running. On the server, the key manager is what's responsible for handling the server certificate and its private key. The key manager is itself usually initialised by the "keystore keystore". "keystore" in Java can have multiple meanings. One of the meaning of keystore is the entity into which keys and certificates can be stored, typically a file. Such a keystore can be used to initialise a trust manager1 (in which case it's referred to as the truststore) or a key manager (in which case it's referred to as a keystore). Sorry, not my choice of names, but that's the way the system properties are called.
When the server is configured with a null key manager, it is configured without any certificate and associated private key. Therefore, it doesn't have any RSA or DSA certificate. Therefore, it won't be able to use any of the *_RSA_* or *_DSS_* cipher suites, whether they've been explicitly enabled or not (they will be disabled automatically by lack of certificate to use with them). This effectively discards any cipher suite enabled by default (or any such cipher suite enabled explicitly anyway). Hence, there is "no cipher suite in common".
In short, an SSLContext on the server side needs to be configured with a certificate and its private key2. This is done by configuring its key manager. In turn, this is often done by using a keystore with a KeyManagerFactory (not a TrustManagerFactory).
1: The trust manager uses local trust anchors (e.g. trusted CA certificates) to evaluate trust in a remote party (i.e. a server trusting a client certificate or a client trusting a server certificate).
2: Some cipher suites supported by the JSSE don't need certificates, but they're either anonymous cipher suites (insecure) or Kerberos cipher suites (which need to be set up differently altogether). Both are disabled by default.
I started following a tutorial that wasn't cased around Android and got this:
System.setProperty("javax.net.ssl.trustStore", "truststore");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory.getDefault();
try {
Socket s = ssf.createSocket("192.168.2.11", 6543);
PrintWriter out = new PrintWriter(s.getOutputStream());
while (true){
out.println("SSL TEST");
Log.d("DATA", "DATA SENT");
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
I guess this boils down to a few questions:
I do not have my own trust store created, but searching through tutorials and things online, I am not sure how to create one. Is there a way I can create or modify a trust store to have the certificates I need in it? (I am using a self-signed certificate if that makes any difference)
How do I make things with the SSL handshake run smoothly? Right now, the error I am getting is:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Honestly, I don't really understand what that means.
What settings or files do I need to modify on my Android device to make sure this connection can happen?
1) It depends. Do you have a self signed cert on the server side and you are trying to validate your identity to the android device? Or are you on the android side trying to validate your idendity to the server? If it is the former , then please see this link: http://www.codeproject.com/KB/android/SSLVerification_Android.aspx?display=Mobile
You want to pay particular attention to where it makes the KeyStore file.
2) The reason you're getting that error is because it doesn't trust the server you are connecting either because you did not create the truststore correctly or you are connecting to a server whose certificate has not been added to the truststore. What exactly are you trying to connect to?
3) Make sure you have the <uses-permission android:name="android.permission.INTERNET" /> in the manifest.xml.
Edit
My apologies, please see the changes I made to the first paragraph.
Here is the part to initialize your keystore and truststore
SSLcontext sslContext = SSLContext.getDefault();
KeyStore trustSt = KeyStore.getInstance("BKS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
InputStream trustStoreStream = this.getResources().openRawResource(R.raw.truststore);
trustSt.load(trustStoreStream, "<yourpassword>".toCharArray());
trustManagerFactory.init(trustStre);
KeyStore keyStore = KeyStore.getInstance("BKS");
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
InputStream keyStoreStream = this.getResources().openRawResource(R.raw.keystore);
keyStore.load(keyStoreStream, "<yourpassword>".toCharArray());
keyManagerFactory.init(keyStore, "<yourpassword>".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
You don't need your own truststore unless the peer is using self-signed certifictes. The JRE ships with a truststore that is used by default, which trusts certificates issued by all the major CAs.