Two-Way SSL - or mutual authentication - is typically dictated in HTTPS by the server. For example, this tutorial explains how to set up WildFly application server to require webservice clients to present a certificate during communication.
However, in our case we need to enforce Two-Way SSL on the client side. That means our client is configured with a client certificate so that it can supply the certificate during handshake. If a server we are connecting to does not ask for the certificate, we want to abort communication.
Descriptions of the SSL handshake like the diagram in the section titled "The SSL Protocol" here (a bit further down) explain how the first thing happening is selection of a cipher suite:
"1. Client hello - The client sends the server information including the
highest version of SSL it supports and a list of the cipher suites it
supports. (TLS 1.0 is indicated as SSL 3.1.) The cipher suite
information includes cryptographic algorithms and key sizes."
On Java side (more specifically: CXF in my case) , it's possible to filter cipher suites("cipherSuitesFilter") - so I thought it would be possible to limit cipher suites to those requiring mutual authentication. But I don't find any links between cipher suites and two-way SSL. For example, this page notes:
authentication algorithm - dictates how server authentication and (if
needed) client authentication will be carried out.
I'm starting to think that means the cipher suite only dictates how client authentication is done, not if client authentication is required.
That leaves me at a dead end. Is there any other way to enforce client authentication on the client side?
Right now the only solution I can think of is finding the right hook method to implement for SSL communication after the handshake has been done, checking if the connection uses client authentication and aborting if it's not. But I'd like to use any kind of common approach for, if such a thing exists.
We didn't find a better solution than the one I already mentioned in my question.
As a client, we can only check whether a connection was established using a client certificate. That does not guarantee that the server thoroughly verified the certificate, just that it requested a certificate.
Our implementation is a custom javax.net.ssl.SSLSocketFactory that extends createSocket methods to check whether javax.net.ssl.SSLSession.getLocalCertificates() returns something. If not, an exception subclass of javax.net.ssl.SSLException is thrown to abort communication.
The socket factory is set via org.apache.cxf.configuration.jsse.TLSClientParameters.setSSLSocketFactory(SSLSocketFactory).
Related
On the server side, it is possible to specify whether you want to send a client-certificate request to the connecting clients:
https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#setWantClientAuth(boolean)
For completeness, you can also specify whether you want to negotiation to continue if the client does not provide a certificate:
https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#setNeedClientAuth(boolean)
My specific question is: given a client application written in java, is it possible to somehow know whether a server requested a client certificate during the negotiation (i.e. the server your application is connecting to was configured with setWantClientAuth(true))?
You can install a KeyManager that does nothing but delegate to the default and set a flag if it was called.
Or if you're directly using SSLSocket or SSLEngine after the handshake you can check .getSession().getLocalCertificates()/getLocalPrincipal(). Thanks to #dave_thompson_085 for that.
This question already has answers here:
Writing a SSL Checker using Java
(2 answers)
Closed 5 years ago.
This question is NOT a duplicate of question pointed to. There is NOWHERE in mentioned question anything about fact that TLS does not perform hostname verification by itself.
I have ActiveMQ instance and client in Java. Client uses JMSTemplate (org.springframework.jms.core.JmsTemplate) with factory org.apache.activemq.ActiveMQSslConnectionFactory. I have created self-signed certificates and with them trust store and keystore. Trust stores and keystores are read by both programs, I checked it by running both programs with
-Djavax.net.debug=all
Now my problem is that it seems that client absolutely ignores server hostname verification. Client connects to ActiveMQ using URL:
ssl://localhost:61616?jms.useCompression=true
Now, I tried to check whether everything will fail as expected if I change CN on ActiveMQ's certificate and well, it didn't went well. I changed to CN to e.g:
CN=google.com
or to:
CN=some.random.xxx333aaa.net.pp
but all these values seem to be OK with Java. Also note that there are no SANs (that is subjectAltNames). What more I tried to connect to ActiveMQ with such certificate but installed on different machine, and it seems that it all works well. Which is NOT what I want.
Also: I have finally uninstalled all Java versions and installed 1.8.0_144, using only JDK installer, installed jce_policy-8 in both places (it installs both JRE and JDK), did the same on remote machine too.
If you will examine RFC 2246 (TLS) and RFC 2818 (HTTPS) you will discover that hostname verification is part of HTTPS, not part of TLS. In TLS it is entirely up to the application to perform an authorization step.
So in fact my question is: how to force hostname verification?
See this answer.
Ok, I think I found an answer. Check this link:
https://issues.apache.org/jira/browse/AMQ-5443
and link mentioned in link above:
https://tersesystems.com/2014/03/23/fixing-hostname-verification/
It seems that TLS against what I thought DOES NOT PERFORM HOSTNAME VERIFICATION. This is absolutely stunning, but it seems that this is exactly the case. If no one will provide better answer I'll accept my own answer.
EDIT: Also see this:
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
and look specifically at this part:
Cipher Suite Choice and Remote Entity Verification
The SSL/TLS protocols define a specific series of steps to ensure a protected connection. However, the choice of cipher suite directly affects the type of security that the connection enjoys. For example, if an anonymous cipher suite is selected, then the application has no way to verify the remote peer's identity. If a suite with no encryption is selected, then the privacy of the data cannot be protected. Additionally, the SSL/TLS protocols do not specify that the credentials received must match those that peer might be expected to send. If the connection were somehow redirected to a rogue peer, but the rogue's credentials were acceptable based on the current trust material, then the connection would be considered valid.
When using raw SSLSocket and SSLEngine classes, you should always check the peer's credentials before sending any data. The SSLSocket and SSLEngine classes do not automatically verify that the host name in a URL matches the host name in the peer's credentials. An application could be exploited with URL spoofing if the host name is not verified.
Protocols such as HTTPS (HTTP Over TLS) do require host name verification. Applications can use HostnameVerifier to override the default HTTPS host name rules. See HttpsURLConnection for more information.
We run a web application with a JVM backend (Java 7 update 75; code is in Scala, but I don't believe this is relevant). We use Google for authentication via Oauth.
There have been a handful of days over the last couple of months on which we have been intermittently unable to authenticate users.
The redirect to and from Google is successful, but when we try to call the token_endpoint at https://www.googleapis.com/oauth2/v4/token to validate the authentication we sometimes get the following exception: javax.net.ssl.SSLHandshakeException: server certificate change is restrictedduring renegotiation.
This comment on another question led me to find a JDK bug that can manifest as this exception (What means "javax.net.ssl.SSLHandshakeException: server certificate change is restrictedduring renegotiation" and how to prevent it?).
My working hypothesis is:
The bug (https://bugs.openjdk.java.net/browse/JDK-8072385) means that only the first entry in the Subject Alternative Name (SAN) list is checked. The exception above is thrown when the hostname being verified is in the SAN list, but not at the top of the list.
Yesterday (27th May 2015 from ) we saw two different certificates being intermittently served from www.googleapis.com. The first (serial 67:1a:d6:10:cd:1a:06:cc) had an SAN list of DNS:*.googleapis.com, DNS:*.clients6.google.com, DNS:*.cloudendpointsapis.com, DNS:cloudendpointsapis.com, DNS:googleapis.com whilst the second (serial 61:db:c8:52:b4:77:cf:78) had a SAN list of: DNS:*.storage.googleapis.com, DNS:*.commondatastorage.googleapis.com, DNS:*.googleapis.com.
Due to the bug in the JVM, we can validate the first certificate, but the exception is thrown with the second (despite being perfectly valid) as *.googleapis.com is not the first entry in the SAN list.
The fix is in the yet to be released 7u85 (no mention of when this will be available).
I've downgraded a single node of our cluster to 7u65, but the certificate seemed to be reverted at around the time we did this (last error we saw was 22:20GMT) so it's hard to pin down an affirmative fix.
Has anyone else experienced this or something similar and have any other workaround other than downgrading (downgrading removes various other SSL/TLS checks)?
I am not really sure that your problem is related to a JVM bug.
There is a fix in Java 6 and above for CVE-2014-6457, "Triple Handshake attack against TLS/SSL connections (JSSE, 8037066)", prevents peer certificates changing during renegotiation.
Problem explanation:
A security vulnerability in all versions of the Transport Layer
Security (TLS) protocol (including the older Secure Socket Layer
(SSLv3)) can allow Man-In-The-Middle (MITM) type attacks where chosen
plain text is injected as a prefix to a TLS connection. This
vulnerability does not allow an attacker to decrypt or modify the
intercepted network communication once the client and server have
successfully negotiated a session between themselves.
However, if the potentially changed certificate is for the same identity as the last seen certificate then the connection is allowed.
Two identities are considered equal in this case:
There is a subject alternative name specified in both certificates which is an IP address and the IP address in both certificates is the same.
There is a subject alternative name specified in both certificates which is a DNS name and the DNS name in both certificates is the same.
The subject and issuer fields are present in both certificates and contain identical subject and issuer values.
In other conditions (the identity of the certificate has changed) then a javax.net.ssl.SSLHandshakeException: server certificate change is restricted during renegotiation exception is raised.
Workaround:
Disable renegotiation (not recommended) applying the following JVM argument: -Djdk.tls.allowUnsafeServerCertChange=true it disables the unsafe server certificate protection.
Disable SSLv3 in outgoing HTTPS connections, Java 7 supports TLSv1.1 and TLSv1.2 in client mode but defaults to using TLSv1 in the TLS handshake. We should use TLSv1.1 and TLSv1.2 in client mode TLS in java 7 as well. Java 8 enables TLSv1.1 and TLSv1.2 in client mode(in addition to SSLv3 and TLSv1) and uses TLSv1.2 by default in TLS handshake. If you are creating the connection programatically and setting a socket factory use TLS instead of SSL.
Anyway, update your post with your google oauth client code before calling the token_endpoint to validate the authentication to see what might be happening.
I'm working on a Server and Client based app which require two way SSL authentication. (Client authenticates server and Server authenticate client both using SSL Certificates.)
I'm quite new to Netty and have few doubts regarding this.
Is two way authentication possible using Netty?
Can it be simply achievable by adding another SslHandler to the pipelinefactories of both server and client?
If the above is true, how can I grab the required SslHandler at the ChannelConnected() method to do the SslHandshake? And Is it possible to invoke the second handshake at the ChannelConected() method by calling the pipeline back again?
Are there any examples I could refer to regarding this?
I really appreciate any help regarding this, answers or a push in right direction.
Is two way authentication possible using Netty?
Yes
Can it be simply achievable by adding another SslHandler to the pipelinefactories of both server and client?
Yes
If the above is true, how can I grab the required SslHandler at the ChannelConnected() method to do the SslHandshake?
You need the setup the keystore and the truststore correctly when creating your SSLContext.
And Is it possible to invoke the second handshake at the ChannelConected() method by calling the pipeline back again?
From memory, client and server authentication is done in the 1st handshake.
On the client, install the client's private key in the keystore and the server's public key in the truststore.
On the server, install the server's private key in the keystore and the client's public key in the truststore.
Are there any examples I could refer to regarding this?
Here's an example I did for websockets. It only shows you how to setup the server keystore. You will have to add a truststore as the 2nd parameter of serverContext.init(kmf.getKeyManagers(), null, null);
Here's a similar example in Scala with trust store setup.
Here's a good java guide on how to setup SSLContext.
Hope this helps.
Two way authentication requires that both server and client have certificates that the other trusts. The client needs to generate a private key, store it in his keystore, and get it signed by somebody that the server's truststore trusts.
It isn't just a matter of what code you write.
SSL is a presentation layer protocol and the SSL handshake happens right after the socket connection is established and the before the application layer gets a usable socket connection. No matter what application you are using, if you have the SSL protocol layer in place then you can work over SSL.
Two way authentication is just a matter of configuration as mentioned by #EJP above. If both the parties can establish and validate each other trust chain then the handshake succeeds. Refer the netty configuration manual for configuring SSL truststores.
I have a requirement to open a tcp socket and authenticate using SSLv3 or TLSv1 IP protocol using X.509 digital certificate.
What does this handshake process involve exactly? I know the each message should be encrypted and signed with my private key. What else?
After successful I've to send POST HTTP requests over the socket.
The server may decide to close this socket if inactive after some time. I need to be able to re-open, authenticate and send requests again.
The certificate given to me is in PKCS12 format with the following information.
Certificate Identification
,Certificate Public Key
,Certificate Private Key
,Certification Authority Chain
I'm fairly new to SSL can someone please provide pointers to how to go about implementing this in java or spring integration.
A good start is to see the javax.net.ssl.HttpsURLConnection javadocs: http://download.oracle.com/docs/cd/E17476_01/javase/1.4.2/docs/api/javax/net/ssl/HttpsURLConnection.html
Also you gonna need to use the keytool command to import the certificate into a keystore.
You don't need to know about the handshake, it is all done for you. Read the JSSE Reference as suggested to see what you +do+ have to worry about.
Regarding TLS/SSL details, for client-certificate authentication, compared with the "normal" hanshake, the server sends an extra CertificateRequest TLS message to the client, which responds with its certificate in a subsequent Certificate TLS message (later on, the client sends a CertificateVerify TLS message where it signs the other messages with its private key, so as to prove to the server that it does indeed have the private key for the public key in the certificate it sent.) Note that, once the handshake has finished, the messages are not encrypted with your private key, but with ephemeral keys shared with the server (agreeing on those keys confidentially is part of the handshake too).
In practice, you need a certificate and its private key, contained in the PKCS#12 file (for example) and to configure the client to send it when connecting to the server (the server will ask for it according to its configuration).
It's easier to assume you'll only need one certificate and won't have to make a choice between a number of certificates, otherwise, you need to set up your own X509TrustManager within the SSLContext.
If all your connections are likely to use this certificate, you may use the default settings, which HttpsURLConnection (and the default SSLSocketFactory) will pick up.
This can be done by:
setting the javax.net.ssl.keyStore, javax.net.ssl.keyStoreType and javax.net.ssl.keyStorePassword system properties on the command line with your settings. I would recommend against that because someone else on the machine could potentially see the command line and your settings by listing the processes (depending on the configuration of the machine),
setting those system properties within your application,
initialising an SSLContext and setting it as the default one via SSLContext.setDefault(..) (Java 6).
Note that .p12 (PKCS#12) files are a supported keystore out of the box, so you don't need to do any conversion with keytool, just use PKCS12 as the store type.
If you need these settings or, you may initialise an SSLContext, create an SSLSocketFactory from it and then configure the instance of HttpsURLConnection (if that's what you're using) with setSSLSocketFactory.
(You may be able to use tools like jSSLutils to help build the SSLContext.)