I have the following scenario: two Java applications are running on the same Tomcat server -- let's call them App A and App B -- and they need to talk to each other via a webservice that is already up and running on App A. This webservice only accepts authenticated clients (clientAuth="true").
After all is configured, my Tomcat, acting as a client, sends an empty certificate chain to the server (which is the same Tomcat). Here's what I have done.
I generated a self-signed certificate for the server using keytool:
keytool -genkeypair -keyalg RSA -keysize 2048 -keystore keystore.jks -alias server
Then exported this certificate:
keytool -exportcert -keystore keystore.jsk -alias server -file server.crt
And added it to the truststore:
keytool -importcert -keystore truststore.jsk -alias server -file server.crt
The SSL negotiation is as follows:
Client says hello
Server says hello and presents its certificate (generated above) for
the client
Client accepts it (since it's in the truststore)
Server requires client certificate by presenting a list of valid DNs (which contains a single entry, the certificate generated above)
Client somehow decides it has no valid certificates and sends an empty cert chain
SSL handshake fails
I didn't post the actual SSL log because the client and the server are the same Tomcat, so everything is a mixed up mess.
What have I done wrong here? Keep in mind that these two apps are on the same server and the cert is self-signed. I also did a test where I generated another self-signed cert, exported it to a .p12 file and installed it in my browser, then called the webservice via URL directly into the address box, it worked flawlessly.
Thanks!
For the record to anyone that gets here...
I was able to do it in two ways. One was to prepare a special configuration file for my client application, named cxf.xml, as explained in this link. The other one was to use code to force the loading of the configuration, as follows:
#WebEndpoint(name = "MyWebServicePort")
public IMyWebService getMyWebServicePort() {
IMyWebService port = super.getPort(new QName("http://my.namespace/", "MyWebServicePort"), IMyWebService.class);
try {
// loads keymanager
File keyStoreFile = new File(System.getProperty("javax.net.ssl.keyStore"));
String keyStorePass = System.getProperty("javax.net.ssl.keyStorePassword");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keyStoreFile), keyStorePass.toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, keyStorePass.toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
// loads trustmanager
File trustStoreFile = new File(System.getProperty("javax.net.ssl.trustStore"));
String trustStorePass = System.getProperty("javax.net.ssl.trustStorePassword");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream(trustStoreFile), trustStorePass.toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(trustStore);
TrustManager[] tm = trustFactory.getTrustManagers();
// configuring the connection
Client cxfClient = ClientProxy.getClient(port);
HTTPConduit httpConduit = (HTTPConduit) cxfClient.getConduit();
TLSClientParameters tlsParams = httpConduit.getTlsClientParameters();
if (tlsParams == null) {
tlsParams = new TLSClientParameters();
}
tlsParams.setKeyManagers(km);
tlsParams.setTrustManagers(tm);
httpConduit.setTlsClientParameters(tlsParams);
} catch (Exception e) {
...
}
return port;
}
I really don't know why it did not load the default config (with the JVM params). It is a mistery: debugging, I found out that when building the SSL connection, it actually uses a DummyX509KeyManager, meaning the configuration was not loaded. Could be a bug? I don't know.
Related
I used this command to create a self-signed certificate for a nginx server running on EC2 instance
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/selfsigned.key -out /etc/ssl/certs/selfsigned.crt
As a common name (e.g. server FQDN or YOUR name) I used Public DNS of EC2 instance that is something like
ec2-somenumber.region.compute.amazonaws.com
I use this code to resolve the trust issues,
I copied the selfsigned.crt to the application raw folder and used it in this way:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.selfsigned);
Certificate ca = cf.generateCertificate(caInput);
// 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 _sslContext = SSLContext.getInstance("TLS");
_sslContext.init(null, tmf.getTrustManagers(), null);
URL url = new URL("https://ec2-somenumber.region.compute.amazonaws.com");
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(_sslContext.getSocketFactory());
Now it's perfectly working on emulator but when I try to debug it on the real device it gives me this error:
javax.net.ssl.SSLPeerUnverifiedException: Hostname ec2-somenumber.region.compute.amazonaws.com not verified
I read a lot of questions here on stackoverlow and I actually don't want to override hostnameVerifier, until I understand why it works on emulator but not on real device.
Do you have any suggestions?
Thanks
If you have the same problem please refer to this link for certificate generation.
To make a self-signed certificate work on real device I needed to specify the Subject Alternative Name like this in the ssl.conf file that I used for the certificate creation
[alt_names]
DNS.1 = ec2-somenumber.region.compute.amazonaws.com
A vendor have given me a .jks to connect to their mq via jms. I am using the following code as a template for my proof of concept.
Connecting to a Websphere MQ in Java with SSL/Keystore
The creation of the Keystore is fine, however when it attempts to create a Truststore it loads a new .jks file. Am i supposed to generate this file or should it have been provided as currently I am unable to create it.
// instantiate a KeyStore with type JKS
KeyStore ks = KeyStore.getInstance("JKS");
// load the contents of the KeyStore
ks.load(new FileInputStream("/home/hudo/hugo.jks"), KSPW);
System.out.println("Number of keys on JKS: "
+ Integer.toString(ks.size()));
// Create a keystore object for the truststore
KeyStore trustStore = KeyStore.getInstance("JKS");
// Open our file and read the truststore (no password)
trustStore.load(new FileInputStream("/home/xwgztu2/xwgztu2.jks"), null);
Thanks
There are a lot of assumptions in this answer (as the question does't provide much of information), but I as well assume it comes with the amount of experience.
To create an SSL connection, the server must have a keypair (private, public key and a certificate bount to the public key) and client must trust the certificate (or its issuer). There is as well an option for mutual SSL (aka client auth ssl), where the client needs its own keypair and certificate and the server needs to trust the client's certificate.
Truststore it loads a new .jks file. Am i supposed to generate this file or should it have been provided as currently I am unable to create it.
The truststore should effectively contain the issuer certificate of the server's certificate (if a self-signed certificate is used, it is the same one).
You can get the certificate by connecting to the service
openssl s_client -connect host:port -showcerts
and then import the returned certificate into a new keystore (using e.g. keytool -importcert command)
keytool -importcert -keystore mytruststore.jks -alias mqserver -file servercert.pem
If the server returns you multiple certificates (certificate chain), you may import them all.
If you are not able to do so, just ask the service provider (someone who deploys the MQ) to provide you the certificate or the truststore.
I did the following things:
+ generate keystore.jks with keytool
+ exported keystore.cer file with keytool
+ imported keystore.cer file into truststore.jks
+ copied keystore.jks and keystore.cer to the client
Then I call my server with
-Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=*
and my client with
-Djavax.net.ssl.keyStore=forclient.jks -Djavax.net.ssl.keyStorePassword=*
The server exposes its interface with the super() call of UnicastRemoteObject
super(PORT,
new SslRMIClientSocketFactory(),
new SslRMIServerSocketFactory(null, null, true));
The Registry stuff does not use any SSL. Why is that not working out?
It DOES work out if I add the keystore VM arguments in the server run config and the trustore VM arguments in the clien. But I really want to know why?
Please understand the aim of keystore and truststore first. Look at the POST . It says
A keystore contains private keys, and the certificates with their corresponding public keys.
A truststore contains certificates from other parties that you expect to communicate with, or from Certificate Authorities that you trust to identify other parties.
So the client SHOULD have truststore so that it trusts the server its interacting with uses server's public key to encrypt the data. Server SHOULD have keystore which stores the private keys which is used to decrypt the data encrypted by corresponding private key by client.
I hope now you got why your application works when you switch keystore and trustore in client-server.
What I'm planning to do is writing a client-server application that uses a SSL (TLS) connection to exchange data.
As the client is downloadable and I can not guarantee access to the keystore I'm looking for a way to import certificates at runtime.
What I need:
A way to import the server's public key/certificate into the client application
A way to import the server's private key/certificate into the server application
What I found out so far:
// load the server's public crt (pem), exported from a https website
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(new
FileInputStream("C:\\certificate.crt"));
// load the pkcs12 key generated with openssl out of the server.crt and server.key (private) (if the private key is stored in the pkcs file, this is not a solution as I need to ship it with my application)
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("C:\\certificate.pkcs"), "password".toCharArray());
ks.setCertificateEntry("Alias", cert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
// create SSLContext to establish the secure connection
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
This does not work for me as I'm getting an error:
java.security.KeyStoreException: TrustedCertEntry not supported at ks.setCertificateEntry("Alias", cert);
Also, I think pkcs12 is used to store private keys which is not what I want.
I'm new to java and I'm really stuck with that problem now.
Thanks in advance,
Kazuo
Sun's implementation of #PKCS12 does not allow to store trusted certificates if are not part of the chain of the private key.
If you need to use #PKCS12 you have to switch to a different provider e.g. Bouncy Castle supports this.
If you do not have a requirement on keystore type you can switch to JKS which is java's keystore and allows to set trusted certificates (i.e. not part of the private key).
For JKS you can use the default provider i.e. SUN.
UPDATE:
So your code would have to change as follows:
//Create a temp keystore with the server certificate
KeyStore ksTemp = KeyStore.getInstance("JKS");
ksTemp.load(null, null);//Initialize it
ksTemp.setCertificateEntry("Alias", cert);
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
// save the temp keystore
ks.store(bOut, password);
//Now create the keystore to be used by jsse
Keystore store = KeyStore.getInstance("JKS");
store.load(new ByteArrayInputStream(bOut.toByteArray()), password);
Now you use the keystore store in your code which has the server's trusted certificate and not the private key.
From the comments in the code I noticed that you have a PKCS12 created using OpenSSL?
If you already have a p12 then you can not use "JKS" for KeyManager.
You will have to use PKCS12 and load it as PKCS12 to use it in the kmf.
So you will have to use 2 types in your app
You don't need to create a certificate at runtime. You don't need to download a keystore to the client. You just need your server certificate signed by a recognized CA.
What you are proposing is insecure. The client has to acquire the certificates to trust via a different means than the channel than the certificate is authenticating. Otherwise there is no reason to trust the certificate, or the channel, or the certificate, ... PKI with CA certs takes care of all that. Your proposal just undermines the scurity built into PKI and SSL. Don't do this.
I want the last of these lines in a standalone application to pass with no exceptions thrown:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial",
"weblogic.jndi.WLInitialContextFactory");
props.setProperty("java.naming.provider.url",
"t3s://localhost:9002");
props.setProperty("java.naming.security.principal",
"<username>");
props.setProperty("java.naming.security.credentials",
"<password>");
Context ctx = new InitialContext(props);
...but I get this information in an exception:
Warning Security BEA-090542 Certificate chain received from localhost - 127.0.0.1 was not trusted causing SSL handshake failure. Check the certificate chain to determine if it should be trusted or not. If it should be trusted, then update the client trusted CA configuration to trust the CA certificate that signed the peer certificate chain. If you are connecting to a WLS server that is using demo certificates (the default WLS server behavior), and you want this client to trust demo certificates, then specify -Dweblogic.security.TrustKeyStore=DemoTrust on the command line for this client.
So, I created a keystore for the ca using this command:
keytool -keystore client.jks -importcert -file cacert.pem
...and referred to it using the property weblogic.security.TrustKeyStore=client.jks
This still doesn't work, most likely because I haven't supplied a password to the keystore. What have I missed? How can I supply this password? (or, how do I create the keystore without setting a password for it?)
Almost two months later, I returned to this issue. After finding this link, I found out that this works:
System.setProperty("weblogic.security.SSL.ignoreHostnameVerification","true");
System.setProperty("java.protocol.handler.pkgs", "weblogic.net");
System.setProperty("weblogic.security.TrustKeyStore","CustomTrust");
System.setProperty("weblogic.security.CustomTrustKeyStoreFileName", "<keystorelocation>");
System.setProperty("weblogic.security.CustomTrustKeyStorePassPhrase","<keystorepassword>");
System.setProperty("weblogic.security.CustomTrustKeyStoreType","JKS");
I only got it working using system properties.