/*
* eID Applet Project.
* Copyright (C) 2009 FedICT.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version
* 3.0 as published by the Free Software Foundation.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, see
* http://www.gnu.org/licenses/.
*/
package be.fedict.eid.applet.io;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import javax.net.SocketFactory;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import be.fedict.eid.applet.View;
/**
* eID Applet specific implementation of an SSL Socket Factory. This is actually
* a decorator component over the original SSL socket factory object.
*
* <p>
* Makes sure that the SSL session doesn't change during eID Applet operations.
* Gives us access to the SSL session identifier and SSL server certificate so
* we can implement secure tunnel binding as part of our authentication
* protocol.
* </p>
*
* @author Frank Cornelis
*
*/
public class AppletSSLSocketFactory extends SSLSocketFactory implements HandshakeCompletedListener {
private View view;
private final SSLSocketFactory originalSslSocketFactory;
private byte[] sslSessionId;
private byte[] encodedPeerCertificate;
/**
* Main constructor.
*
* @param view
* @param originalSslSocketFactory
*/
public AppletSSLSocketFactory(View view, SSLSocketFactory originalSslSocketFactory) {
this.view = view;
this.originalSslSocketFactory = originalSslSocketFactory;
}
private void setView(View view) {
this.view = view;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
Socket socket = this.originalSslSocketFactory.createSocket(s, host, port, autoClose);
/*
* Important here not to try to access the SSL session identifier via
* getSession. This can cause problems when sitting behind an HTTP
* proxy. The only way to get access to the SSL session identifier is
* via the TLS handshake completed listener.
*/
installHandshakeCompletedListener(socket);
return socket;
}
private void installHandshakeCompletedListener(Socket socket) throws IOException {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.addHandshakeCompletedListener(this);
}
@Override
public String[] getDefaultCipherSuites() {
return this.originalSslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return this.originalSslSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket socket = this.originalSslSocketFactory.createSocket(host, port);
installHandshakeCompletedListener(socket);
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket socket = this.originalSslSocketFactory.createSocket(host, port);
installHandshakeCompletedListener(socket);
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
Socket socket = this.originalSslSocketFactory.createSocket(host, port, localHost, localPort);
installHandshakeCompletedListener(socket);
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
Socket socket = this.originalSslSocketFactory.createSocket(host, port, localHost, localPort);
installHandshakeCompletedListener(socket);
return socket;
}
/**
* Gives back the SSL session identifier.
*
* @return the SSL session Id.
*/
public byte[] getSessionId() {
if (null == this.sslSessionId) {
throw new IllegalStateException("SSL session identifier unknown");
}
return this.sslSessionId;
}
/**
* Gives back the DER encoded SSL server certificate.
*
* @return
*/
public byte[] getEncodedPeerCertificate() {
if (null == this.encodedPeerCertificate) {
throw new IllegalStateException("SSL peer certificate unknown");
}
return this.encodedPeerCertificate;
}
@Override
public Socket createSocket() throws IOException {
Socket socket = this.originalSslSocketFactory.createSocket();
installHandshakeCompletedListener(socket);
return socket;
}
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
String cipherSuite = event.getCipherSuite();
this.view.addDetailMessage("SSL handshake finish cipher suite: " + cipherSuite);
SSLSession sslSession = event.getSession();
byte[] sslSessionId = sslSession.getId();
if (null != this.sslSessionId && false == Arrays.equals(this.sslSessionId, sslSessionId)) {
/*
* This could also be caused by an SSL session renewal.
*/
this.view.addDetailMessage("SSL session Id mismatch");
}
this.sslSessionId = sslSessionId;
try {
Certificate[] peerCertificates = sslSession.getPeerCertificates();
this.encodedPeerCertificate = peerCertificates[0].getEncoded();
} catch (SSLPeerUnverifiedException e) {
this.view.addDetailMessage("SSL peer unverified");
} catch (CertificateEncodingException e) {
this.view.addDetailMessage("certificate encoding error: " + e.getMessage());
}
}
public static SocketFactory getDefault() {
SSLSocketFactory sslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
return sslSocketFactory;
}
/**
* Installs this socket factory within the JRE.
*
* @param view
*/
public static void installSocketFactory(View view) {
SSLSocketFactory sslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
if (false == sslSocketFactory instanceof AppletSSLSocketFactory) {
/*
* Install a new one.
*/
AppletSSLSocketFactory appletSslSocketFactory = new AppletSSLSocketFactory(view, sslSocketFactory);
HttpsURLConnection.setDefaultSSLSocketFactory(appletSslSocketFactory);
} else {
/*
* Make sure that the existing one reports to us.
*/
AppletSSLSocketFactory appletSslSocketFactory = (AppletSSLSocketFactory) sslSocketFactory;
appletSslSocketFactory.setView(view);
}
}
/**
* Returns the actual SSL session identifier.
*
* @return
*/
public static byte[] getActualSessionId() {
AppletSSLSocketFactory appletSslSocketFactory = getAppletSSLSocketFactory();
byte[] sessionId = appletSslSocketFactory.getSessionId();
return sessionId;
}
private static AppletSSLSocketFactory getAppletSSLSocketFactory() {
SSLSocketFactory sslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
if (false == sslSocketFactory instanceof AppletSSLSocketFactory) {
throw new SecurityException("wrong SSL socket factory");
}
// TODO: the AppletSSLSocketFactory is shared across all eID Applets...
AppletSSLSocketFactory appletSslSocketFactory = (AppletSSLSocketFactory) sslSocketFactory;
return appletSslSocketFactory;
}
/**
* Gives back the actual DER encoded SSL server certificate.
*
* @return
*/
public static byte[] getActualEncodedServerCertificate() {
AppletSSLSocketFactory appletSslSocketFactory = getAppletSSLSocketFactory();
byte[] encodedPeerCertificate = appletSslSocketFactory.getEncodedPeerCertificate();
return encodedPeerCertificate;
}
}