I am very new to Java security, so the answer to this might be obvious. I am trying to perform a simple handshake between a client and a server and I want them to handshake using either TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384. According to everywhere I read these ciphers are supported in Java 8 and I have installed JCE Unlimited Strength Jurisdiction Policy Files. So when I print out all the enabled ciphers in my Java installation, those two ciphers are present, which means they are enabled by default. But for some reason the handshake fails because client and server have no cipher suites in common. I enabled TLSv1.2 protocol as well. The client's public key has been imported into the server's trust store and the handshake succeed for other ciphers such as TLS_RSA_WITH_AES_128_CBC_SHA256 etc. I am running Java 8 v1.8.0_60. What else am I missing?
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
public class GCMCiphersJava8Test {
private static final String SERVER_KEY_STORE = "testkeystore.jks";
private static final String CLIENT_KEY_STORE = "testtruststore.jks";
private static final String HOST = "localhost";
private static final String PASSWORD = "Pa55word";
private static final int SSL_PORT = 8443;
private static final String[] TLS_12 = new String[]{"TLSv1.2"};
private static String serverKeyStorePath = null;
private static String clientKeyStorePath = null;
private Server server = null;
#BeforeClass
public static void setup() {
serverKeyStorePath = GCMCiphersJava8Test.class.getResource(SERVER_KEY_STORE).getFile();
clientKeyStorePath = GCMCiphersJava8Test.class.getResource(CLIENT_KEY_STORE).getFile();
}
#Test
public void testGCMCiphersInJava8() throws Exception{
SSLSession session = null;
startServer(TLS_12, null, new String[]{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, null);
SSLSocket sslSocket = createSslSocket(TLS_12, null, new String[]{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, null);
if (this.server.isRunning()){
session = sslSocket.getSession();
}
assertEquals("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", session.getCipherSuite());
}
private void startServer(String[] includeProtocols, String[] excludeProtocols, String[] includeCiphers, String[] excludeCiphers) throws Exception{
this.server = new Server();
SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
ssl_connector.setPort(SSL_PORT);
SslContextFactory cf = ssl_connector.getSslContextFactory();
cf.setKeyStorePath(serverKeyStorePath);
cf.setKeyStorePassword(PASSWORD);
cf.setKeyManagerPassword(PASSWORD);
if (includeCiphers != null){
cf.setIncludeCipherSuites(includeCiphers);
}
if (excludeCiphers != null){
cf.setExcludeCipherSuites(excludeCiphers);
}
if (includeProtocols != null){
cf.setIncludeProtocols(includeProtocols);
}
if (excludeProtocols != null){
cf.setExcludeProtocols(excludeProtocols);
}
this.server.setConnectors(new Connector[]{ssl_connector});
this.server.setHandler(new AbstractHandler() {
#Override
public void handle(String target,Request baseRequest,HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("<h1>Hello World</h1>");
}
});
this.server.start();
}
#After
public void stopServer() throws Exception{
this.server.stop();
}
private SSLSocket createSslSocket(String[] includeProtocols, String[] excludeProtocols, String[] includeCiphers, String[] excludeCiphers){
SSLSocket sslSocket = null;
try {
System.setProperty("javax.net.ssl.trustStore", clientKeyStorePath);
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
sslSocket = (SSLSocket) factory.createSocket(HOST, SSL_PORT);
if (includeCiphers != null){
sslSocket.setEnabledCipherSuites(includeCiphers);
}
if (includeProtocols != null){
sslSocket.setEnabledProtocols(includeProtocols);
}
sslSocket.addHandshakeCompletedListener(e -> {
System.out.println("Handshake succesful!");
System.out.println("Using cipher suite: " + e.getCipherSuite());
});
} catch (Exception e) {
e.printStackTrace();
}
return sslSocket;
}
}
A TLS cipher suite has 4 parts, all of which it must specify. They are:
"Key Exchange" which has 3 sane options:
"RSA" which means client generates random, RSA-encrypts the random using server's public key, sends to server. Does not provide PFS, deprecated.
"DHE" which means classic (Final Field) DHE. Older software limited to insecure 1024 bit groups, not recommended.
"ECDHE" which is the only one that's recommended.
"Authentication" which has 3 sane options:
"aRSA" which means RSA signature. Before TLS 1.3 uses pkcs#1v1.5 instead of RSASSA-PSS, no longer recommended, 99.9% sites use this.
"ECDSA" which means ECDSA signatures which requires server to have ECDSA certificate and key. Requires strong CSPRNG during handshake or private key is compromised! Google and Facebook and Cloudflare use this.
"EdDSA" which is going into TLS 1.3 and is going to be the recommended one.
"encryption" which has quite a few usable options:
AES-GCM, an AEAD mode.
chacha20-poly1305, also an AEAD mode. In Google and Cloudflare servers now and in TLS 1.3.
AES (CBC), deprecated.
3DES CBC, deprecated.
Many more. Only AEAD cipher suites are recommended, the rest are deprecated.
"Hash" or "MAC" which has 3 sane options:
(HMAC-)SHA1, still secure.
(HMAC-)SHA256, used for paranoia.
(HMAC-)SHA384, used for paranoia.
But note AEAD cipher suites like GCM (and chacha20-poly1305) actually use their own MAC (GMAC for GCM) so the "MAC" of the cipher suite is only used as PRF for symmetric key generation, not as a MAC, and TLS 1.2 mandates PRF be at least SHA-256.
Neither AES-GCM nor ECDHE impose any constraints on the certificate or key.
ECDSA requires the server to have ECDSA certificate and key.
Thus, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 and TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 work with RSA certs and are what everyone recommends you deploy today if you don't have an ECDSA cert.
I figured it out. To use GCM-based ciphers I needed to generate a keypair using Elliptic Curve option in keytool. I foolishly used RSA one I had before.
generate keypair using EC and 256 key size:
keytool -genkeypair -alias sergey -keyalg EC -keysize 256 -validity 365 -keystore testkeystore.jks -storepass <insertPasswordHere>
export the key into the certificate:
keytool -exportcert -keystore testkeystore.jks -storepass <insertPasswordHere> -file testCert.crt -alias <insertAliasHere>
import that certificate into the server's trust store:
keytool -importcert -trustcacerts -file testCert.crt -alias <sameAliasAsAbove> -keystore testtruststore.jks -storepass <insertPasswordHere>
Related
I am working on a code that connects to slack through a proxy which act as a MITM and replaces slack cert with its own self signed cert. I added proxy's cert into a trust store and configured my RestTemplate to use the trust store:
def sslContext = new SslContextBuilder().withTrustStore(trustStoreResource, trustStorePassword).build()
def proxy = proxyEnabled ? new HttpHost(proxyHost, proxyPort) : null
def httpClient = HttpClients.custom().setProxy(proxy).setSSLContext(sslContext).build()
def result = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient))
That works fine. However, on my local I don't go through the proxy and connect to slack directly. In other words, the httpClient in the above code would be configured with SSLContext but not proxy. I was expecting this to be fine since Slack's cert is signed with a valid root CA but my code fails to verify Slack's cert.
I am assuming this is because my trustore but I am confused as why this is happening. Is it happening because root CAs are not imported in my trustsore? If so, how would I do that without having to maintain the root CAs?
I understand that locally I can refrain from setting up a trust store but I would like to avoid adding branches in the code if possible.
What I finally ended up doing was to use the implementation in https://gist.github.com/JensRantil/9b7fecb3647ecf1e3076 to combine system's default trust store with mine and then used the following class to build my SSL context. It's a shame HttpClient doesn't offer this but there might be a good reason for it.
import org.springframework.core.io.Resource
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import java.security.KeyStore
class SslContextBuilder {
private KeyManager[] keyManagers = []
private TrustManager[] trustManagers = []
SslContextBuilder withKeyStore(Resource resource, String password) {
def keyStore = KeyStore.getInstance('JKS')
keyStore.load(resource.getInputStream(), password.chars)
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmfactory.init(keyStore, password.chars)
KeyManager[] kms = kmfactory.getKeyManagers()
keyManagers += kms ? kms : []
this
}
SslContextBuilder withTrustStore(Resource resource, String password) {
def trustStore = KeyStore.getInstance('JKS')
trustStore.load(resource.getInputStream(), password.chars)
def tss = CompositeX509TrustManager.getTrustManagers(trustStore)
trustManagers += tss ? tss : []
this
}
SSLContext build() {
def sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null)
sslContext
}
}
I have logstash-6.5.4 (with ssl), web and scheduler in my local (hostname: webbox) and kafka-2.0 (with ssl) on another (hostname: kafkabox).
I am not able to receive message in kafka topic when message is sent from logstash.
Neither error message is displayed not message is sent to kafka topic. I tried to import logstash.crt into kafka's truststore but it also didn't worked.
Created logstash.crt and logstash.key with below command.
sudo openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout /etc/logstash/logstash.key -out /etc/logstash/logstash.crt
Imported the logstash.crt into kafka's truststore file also and tried.
keytool -import -alias logstash -file logstash.crt -keystore cacerts
Logstash conf file is given below...
input {
tcp {
host=>"0.0.0.0"
port=>5514
type=>"syslogType"
ssl_enable=>true
ssl_cert=>"/etc/logstash/logstash.crt"
ssl_key=>"/etc/logstash/logstash.key"
ssl_verify=>false
}
}
filter {
}
output {
kafka {
bootstrap_servers=>"kafkabox:9093"
codec=>"json_lines"
topic_id=>"a_test"
ssl_keystore_location=>"keystore file"
ssl_keystore_password=>"changeit"
ssl_key_password=>"changeit"
ssl_truststore_location=>"truststore file"
ssl_truststore_password=>"changeit"
security_protocol=>"SSL"
}
}
Expecting message is sent from logstash (with SSL) to kafka (with SSL).
Java Code to connect to logstash which internally failing to send message to kafka topics (in ssl mode).
public class LogstashClient {
private static String message = "<86>Jun 25 14:32:25 webbox sshd[7517]: Failed password for root from 196.165.132.192 port 45691 ssh2";
public static void main(String[] args) throws Exception {
nonSSL();
//SSL();
}
private static void SSL() throws Exception {
// logstash.crt is directly imported into kafka's truststore
// Below <<Client Truststore>> will also have logstash.crt imported for handshaking while connecting
System.setProperty("javax.net.ssl.trustStore", "<<Client Truststore>>");
System.setProperty("javax.net.ssl.trustStorePassword", "test1234");
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 5514);
System.out.println("Handshaking...");
socket.startHandshake();
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
boolean checkError = printWriter.checkError();
printWriter.println(message);
}
private static void nonSSL() throws Exception {
Socket socket = new Socket("localhost", 5514);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println(message);
}
}
Thanks,
RK,
Server:
TLS Version: v1.2
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Client:
JRE 1.7
I am receiving the below error when I try to connect to the Server from Client through SSL directly:
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
The below code enables TLSv1.2
Set<String> enabledTLSSet = new HashSet<String>(Arrays.asList(sslsocket.getEnabledProtocols()));
enabledTLSSet.add("TLSv1.2");
sslsocket.setEnabledProtocols(enabledTLSSet.toArray(new String[enabledTLSSet.size()]));
The below code enables TLS_RSA_WITH_AES_256_CBC_SHA256 Cipher Suite:
Set<String> enabledCipherSuitesSet = new HashSet<String>(Arrays.asList(sslsocket.getEnabledCipherSuites()));
enabledCipherSuitesSet.add("TLS_RSA_WITH_AES_256_CBC_SHA256");
sslsocket.setEnabledCipherSuites(enabledCipherSuitesSet.toArray(new String[enabledCipherSuitesSet.size()]));
After doing both of the above from Java code, I'm able to connect to the server successfully through SSL.
Is it possible to enable/force TLSv1.2 and TLS_RSA_WITH_AES_256_CBC_SHA256 in Java 7 without changing any Java Code through properties, parameters or Debug props?
I tried all of the below properties in all forms and combinations (enabling and disabling) and failed.
-Dhttps.protocols=TLSv1.2
-Dhttps.cipherSuites=TLS_RSA_WITH_AES_256_CBC_SHA256
-Ddeployment.security.TLSv1.2=true
I'm executing the program similar to the below:
java -jar -Dhttps.protocols=TLSv1.2 -Dhttps.cipherSuites=TLS_RSA_WITH_AES_256_CBC_SHA256 Ddeployment.security.TLSv1.2=true -Djavax.net.debug=ssl:handshake SSLPoker.jar <SERVER> 443
SSLPoker contains the below code:
package com.ashok.ssl;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
/**
* Establish a SSL connection to a host and port, writes a byte and prints the response - Ashok Goli. See
* http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services
*/
public class SSLPoke {
/**
* The main method.
* Usage: $java -jar SSLPoker.jar <host> <port>
*
* #param args the arguments
*/
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: " + SSLPoke.class.getName() + " <host> <port>");
System.exit(1);
}
try {
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket =
(SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1]));
InputStream in = sslsocket.getInputStream();
OutputStream out = sslsocket.getOutputStream();
// Write a test byte to get a reaction :)
out.write(1);
while (in.available() > 0) {
System.out.print(in.read());
}
System.out.println("Successfully connected");
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
Any pointers how to achieve this with no Java code changes would be much appreciated.
It is only possible if you use a simple HTTPS connection (not SSL Sockets) using the properties
-Dhttps.protocols=TLSv1.2
-Dhttps.cipherSuites=TLS_RSA_WITH_AES_256_CBC_SHA256
See the post at http://fsanglier.blogspot.com.es/
Java 7 introduced support for TLS v1.2 (refer to
http://docs.oracle.com/javase/7/docs/technotes/guides/security/enhancements-7.html)
BUT does not enable it by default. In other words, your client app
must explicitly specify "TLS v1.2" at SSLContext creation, or
otherwise will just not be able to use it.
If you need to use directly secure socket protocol, create a "TLSv1.2" SSLContext at application startup and use the SSLContext.setDefault(ctx) call to register that new context as the default one.
SSLContext context = SSLContext.getInstance("TLSv1.2");
SSLContext.setDefault(context);
The JREs disable all 256-bit crypto by default. To enable you can download Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files here: http://www.oracle.com/technetwork/java/javase/downloads/index.html
Replace the local_policy.jar and US_export_policy.jar jars files into your lib/security in jre directory.
It looks like current JRE's ship both the limited and unlimited policy files under the JRE's install folder in lib/security, each in separate sub folders. By default, in lib/security/java.security, the limited policy is used by default. But if you uncomment the crypto.policy=unlimited line, that will allow Java to use the unlimited policy files and enable the 256-bit ciphers/algorithms.
I have spring-boot Tomcat server for secure websocket connections. The server accepts Android 4.4, iOS, Firefox, and Chrome clients without failure with an authority-signed certificate. Android 5.0, however, fails the SSL handshake.
Caused by: javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:436)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:1006)
at org.glassfish.grizzly.ssl.SSLConnectionContext.unwrap(SSLConnectionContext.java:172)
at org.glassfish.grizzly.ssl.SSLUtils.handshakeUnwrap(SSLUtils.java:263)
at org.glassfish.grizzly.ssl.SSLBaseFilter.doHandshakeStep(SSLBaseFilter.java:603)
at org.glassfish.grizzly.ssl.SSLFilter.doHandshakeStep(SSLFilter.java:312)
at org.glassfish.grizzly.ssl.SSLBaseFilter.doHandshakeStep(SSLBaseFilter.java:552)
at org.glassfish.grizzly.ssl.SSLBaseFilter.handleRead(SSLBaseFilter.java:273)
at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112)
at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:561)
at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
at java.lang.Thread.run(Thread.java:818)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0xa1f34200: Failure in SSL library, usually a protocol error
error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message (external/openssl/ssl/s3_both.c:498 0xac526e61:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake_bio(Native Method)
at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:423)
I think the problem is with TLS or the cipher suites due to changes in Android 5.0 Lollipop, and not with the certificates because the other clients connect, but I cannot figure out how to tell what is happening on the client side of the connection because SSL debugging does not appear to be supported on Android. The problem is likely very similar to this one, which is also not resolved yet but suggests the problem is with cipher suites. The Android bugs 88313 81603 developer-preview-1989 seem to indicate the Android implementation is correct but server configuration or implementation of cipher suites may not be.
I have set the following server cipher suites
server.ssl.ciphers = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA
In particular, the TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA is on the list of supported protocols for Android for API 11+.
I verified the server supports this
openssl s_client -connect server:port
which returns
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-SHA
There is a slight mismatch in names between openssl and java, but the openssl documentation says these are the same cipher suite.
My server supports and negotiates first a cipher suite with the openssl client that is compatible with Android 5.0. I expect Android 5.0 to connect without issue, but it fails.
Has anyone successfully connected Android 5.0 secure websocket connections to Tomcat? Are there cipher suites that are known to work? Is there a way to debug the Android client side SSL implementation?
UPDATE
Network trace results:
SYN -->
<-- SYN, ACK
ACK -->
<-- Data
ACK -->
<-- certificates, SSL/TLS params? 1
<-- 2
<-- 3
<-- 4
ACK -->
ACK -->
ACK -->
FIN(!), ACK -->
When the Android 5.0 device (a Nexus 5) receives the server certificate information sent in 4-5 packets, it responds with a variable number (2-4) ACKs then a FIN, ACK. In the successful trace, the client does not send a FIN. The Android 5 client does not like something it gets from the server.
For the failure, the server SSL debugging info says:
http-nio-8080-exec-10, called closeOutbound()
http-nio-8080-exec-10, closeOutboundInternal()
http-nio-8080-exec-10, SEND TLSv1.2 ALERT: warning, description = close_notify
http-nio-8080-exec-10, WRITE: TLSv1.2 Alert, length = 2
[Raw write]: length = 7
0000: 15 03 03 00 02 01 00
UPDATE 2
Here is a bare-bones Tyrus Android application to use
package edu.umd.mindlab.androidssldebug;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import org.glassfish.tyrus.client.ClientManager;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URI;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
#ClientEndpoint
public class MainActivity extends ActionBarActivity {
public static final String TAG = "edu.umd.mindlab.androidssldebug";
final Object annotatedClientEndpoint = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onStart(){
super.onStart();
final Object annotatedClientEndpoint = this;
new Thread(new Runnable(){
#Override
public void run() {
try {
URI connectionURI = new URI("wss://mind7.cs.umd.edu:8080/test");
ClientManager client = ClientManager.createClient();
Object clientEndpoint = annotatedClientEndpoint;
client.connectToServer(clientEndpoint, connectionURI);
}
catch(Exception e){
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteStream);
e.printStackTrace(printStream);
final String message = byteStream.toString();
Log.e(TAG, message);
e.printStackTrace();
runOnUiThread(new Runnable() {
public void run() {
TextView outputTextView = (TextView) findViewById(R.id.outputTextView);
outputTextView.setText(message);
}
});
}
}
}).start();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#OnOpen
public void onOpen(Session session) {
Log.i(TAG, "opened");
runOnUiThread(new Runnable() {
public void run() {
TextView outputTextView = (TextView) findViewById(R.id.outputTextView);
outputTextView.setText("opened");
}
});
}
#OnMessage
public void onMessage(String message, Session session) {
Log.i(TAG, "message: " + message);
}
#OnClose
public void onClose(Session session, CloseReason closeReason) {
Log.i(TAG, "close: " + closeReason.toString() );
}
#OnError
public void onError(Session session, Throwable t) {
final String message = "error: " + t.toString();
Log.e(TAG, message);
runOnUiThread(new Runnable() {
public void run() {
TextView outputTextView = (TextView) findViewById(R.id.outputTextView);
outputTextView.setText(message);
}
});
}
}
error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message (external/openssl/ssl/s3_both.c:498 0xac526e61:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake_bio(Native Method)
at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:423)
0x1408E0F4 is:
$ openssl errstr 0x1408E0F4
error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message
It shows up in the OpenSSL sources at a couple of places:
$ cd openssl-1.0.1l
$ grep -R SSL3_GET_MESSAGE *
ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE);
ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE);
ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_EXCESSIVE_MESSAGE_SIZE);
ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_EXCESSIVE_MESSAGE_SIZE);
ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,ERR_R_BUF_LIB);
Here's the code I believe is causing the trouble (line numbers have changed, and the SSLerr is at 491):
/* Obtain handshake message of message type 'mt' (any if mt == -1),
* maximum acceptable body length 'max'.
* The first four bytes (msg_type and length) are read in state 'st1',
* the body is read in state 'stn'.
*/
long ssl3_get_message(SSL *s, int st1, int stn, int mt, long max, int *ok)
{
...
/* s->init_num == 4 */
if ((mt >= 0) && (*p != mt))
{
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE);
goto f_err;
}
...
But I'm not sure what causes that particular problem. See this question on the OpenSSL User List at SSL_F_SSL3_GET_MESSAGE and SSL_R_UNEXPECTED_MESSAGE.
EDIT: according to the Android source for s3_both.c, that is the code that's triggering the issue.
-----
OK, looking at the file successful.pcap and unsuccessful.pcap, the good client is using TLS 1.0 while the misbehaving client is using TLS 1.2. But I don't see anything offensive that would cause the client to close the connection while processing the four messages (Server Hello, Certificate, Server Key Exchange, Server Hello Done) in the Record.
-----
Based on the ServerKeyExchange message:
The server selected the client's offering of secp521r1. You might want to use secp256. That's most interoperable right now. Also see Is the limited elliptic curve support in rhel/centos/redhat openssl robust enough?.
-----
OpenSSL 1.0.1e FIPS used by the server has suffered a few problems. See, for example:
Binary curves broken in FIPS mode
Crash when using TLS 1.2 caused by use of incorrect hash algorithm
If possible, you might want to upgrade it to something newer.
-----
Is there a way to debug the Android client side SSL implementation?
I think this is an easier question. Use a custom SSLSocketFactory like SSLSocketFactoryEx. It will allow you to try different protocols, cipher suites and settings. But its trial-and-error.
Otherwise, you would need to grab a copy of the OpenSSL source code used by Android 5.0 (including patches). I don't know how to get that and ensure it builds like mainline OpenSSL (effectively, you need to build s_client using Android sources with debugging information).
This might be helpful: OpenSSL on Android. From the looks of the diffs, it appears Android is using OpenSSL 1.0.0. (Some of the patches in the patch/ directory specifically call out 1.0.0b).
This is confirmed to be caused by an Android 5.0 bug. It is unclear to me currently whether there is also a problem in Tyrus websocket or Grizzly.
See also: 93740 and preview 328.
The suggested fix at TYRUS-402 resolves this. I have opened a corresponding Grizzly Bug GRIZZLY-1827 which has the corresponding patch.
Update: The bug GRIZZLY-1827 has been fixed.
I have a Java Key Store where I store certificates for each of my customer's sub-domain. I am planning to use the server alias to differentiate between multiple customers in the key store as suggested here. Play framework 1.2.7 uses Netty's SslHandler to support SSL on the server-side. I tried implementing a custom SslHttpServerContextFactory that uses this solution.
import play.Play;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Properties;
public class CustomSslHttpServerContextFactory {
private static final String PROTOCOL = "SSL";
private static final SSLContext SERVER_CONTEXT;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
SSLContext serverContext = null;
KeyStore ks = null;
try {
final Properties p = Play.configuration;
// Try to load it from the keystore
ks = KeyStore.getInstance(p.getProperty("keystore.algorithm", "JKS"));
// Load the file from the conf
char[] certificatePassword = p.getProperty("keystore.password", "secret").toCharArray();
ks.load(new FileInputStream(Play.getFile(p.getProperty("keystore.file", "conf/certificate.jks"))),
certificatePassword);
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, certificatePassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(ks);
final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManagerWrapper(origKm);
// Initialize the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(new KeyManager[]{km}, tmf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
}
public static SSLContext getServerContext() {
return SERVER_CONTEXT;
}
public static class X509KeyManagerWrapper implements X509KeyManager {
final X509KeyManager origKm;
public X509KeyManagerWrapper(X509KeyManager origKm) {
this.origKm = origKm;
}
public String chooseServerAlias(String keyType,
Principal[] issuers, Socket socket) {
InetAddress remoteAddress = socket.getInetAddress();
//TODO: Implement alias selection based on remoteAddress
return origKm.chooseServerAlias(keyType, issuers, socket);
}
#Override
public String chooseClientAlias(String[] keyType,
Principal[] issuers, Socket socket) {
return origKm.chooseClientAlias(keyType, issuers, socket);
}
#Override
public String[] getClientAliases(String s, Principal[] principals) {
return origKm.getClientAliases(s, principals);
}
#Override
public String[] getServerAliases(String s, Principal[] principals) {
return origKm.getServerAliases(s, principals);
}
#Override
public X509Certificate[] getCertificateChain(String s) {
return origKm.getCertificateChain(s);
}
#Override
public PrivateKey getPrivateKey(String s) {
return origKm.getPrivateKey(s);
}
}
}
But, this approach did not work for some reason. I get this message in my SSL debug log.
X509KeyManager passed to SSLContext.init(): need an X509ExtendedKeyManager for SSLEngine use
This is the SSL trace, which fails with "no cipher suites in common". Now, I switched the wrapper to:
public static class X509KeyManagerWrapper extends X509ExtendedKeyManager
With this change, I got rid of the warning, but I still see the same error as before "no cipher suites in common" and here is the SSL trace. I am not sure why the delegation of key manager won't work.
Some more information that may be useful in this context.
Netty uses javax.net.ssl.SSLEngine to support SSL in NIO server.
As per the recommendation in this bug report, it is intentional that X509ExtendedKeyManager must be used with an SSLEngine. So, the wrapper must extend X509ExtendedKeyManager.
This is hindering me to move further with the custom alias selection logic in X509KeyManagerWrapper. Any clues on what might be happening here? Is there any other way to implement this in Netty/Play? Appreciate any suggestions.
SSLEngine uses the chooseEngineServerAlias method to pick the certificate to use (in server mode) - not the chooseServerAlias method.
The default chooseEngineServerAlias implementation actually returns null, which is what causes the "no cipher suites in common" message - you need a certificate to know which cipher suites can be used (e.g. ECDSA can only be used for authentication if the certificate has an ECC public key, etc.) There are actually some cipher suites which can be used without a certificate, however, these are typically disabled as they are vulnerable to MITM attacks.
Therefore, you should also override chooseEngineServerAlias, and implement your logic to select the certificate based on the IP address there. As Netty only uses SSLEngine, what chooseServerAlias does doesn't matter - it'll never be called.
Java 8 also has support for server-side SNI, which allows you to use several certificates across many hostnames with a single IP address. Most web browsers support SNI - the notable exceptions are IE running on Windows XP and some old versions of Android, however, usage of these is declining. I have created a small example application demonstrating how to use SNI in Netty on GitHub. The core part of how it works is by overriding chooseEngineServerAlias - which should give you enough hints, even if you want to use the one certificate per IP address technique instead of SNI.
(I posted a similar answer to this on the Netty mailing list, where you also asked this question - however, my post seems to have not yet been approved, so I thought I'd answer here too so you can get an answer sooner.)