/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.commons.utils; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.apache.commons.lang3.StringUtils; import org.apache.http.conn.ssl.DefaultHostnameVerifier; import at.gv.egovernment.moa.id.commons.ex.MOAHttpProtocolSocketFactoryException; import at.gv.egovernment.moa.id.commons.utils.ssl.SSLConfigurationException; import at.gv.egovernment.moa.util.MiscUtil; import at.gv.egovernment.moaspss.logging.Logger; import iaik.pki.PKIException; /** * @author tlenz * */ public class MOAHttpProtocolSocketFactory implements SecureProtocolSocketFactory { private SSLSocketFactory sslfactory = null; private boolean verifyHostName = true; /** * SSL Protocol socket factory * * @param url * @param trustStoreURL * @param acceptedServerCertURL * @param chainingMode Specifies the ChainingMode for SSL certificate trust validation * @param checkRevocation Enables / Disables certificate revocation checks * @param revocationMethodOrder * @param verifyHostName Enables / Disables hostName verfication * @throws MOAHttpProtocolSocketFactoryException */ public MOAHttpProtocolSocketFactory ( String url, boolean useStandardJavaTrustStore, String trustStoreURL, String acceptedServerCertURL, String chainingMode, boolean checkRevocation, String[] revocationMethodOrder, boolean verifyHostName) throws MOAHttpProtocolSocketFactoryException { internalInitialize(url, useStandardJavaTrustStore, null, trustStoreURL, acceptedServerCertURL, chainingMode, checkRevocation, revocationMethodOrder); this.verifyHostName = verifyHostName; } /** * SSL Protocol socket factory * * @param url * @param certStoreDirectory * @param trustStoreURL * @param acceptedServerCertURL * @param chainingMode Specifies the ChainingMode for SSL certificate trust validation * @param checkRevocation Enables / Disables certificate revocation checks * @param revocationMethodOrder * @param verifyHostName Enables / Disables hostName verfication * @throws MOAHttpProtocolSocketFactoryException */ public MOAHttpProtocolSocketFactory(String url, boolean useStandardJavaTrustStore, String certStoreDirectory, String trustStoreURL, String acceptedServerCertURL, String chainingMode, boolean checkRevocation, String[] revocationMethodOrder, boolean verifyHostName) throws MOAHttpProtocolSocketFactoryException { internalInitialize(url, useStandardJavaTrustStore, certStoreDirectory, trustStoreURL, acceptedServerCertURL, chainingMode, checkRevocation, revocationMethodOrder); this.verifyHostName = verifyHostName; } private void internalInitialize(String url, boolean useStandardJavaTrustStore, String certStoreDirectory, String trustStoreURL, String acceptedServerCertURL, String chainingMode, boolean checkRevocation, String[] revocationMethodOrder) throws MOAHttpProtocolSocketFactoryException { try { this.sslfactory = at.gv.egovernment.moa.id.commons.utils.ssl.SSLUtils.getSSLSocketFactory( url, useStandardJavaTrustStore, certStoreDirectory, trustStoreURL, acceptedServerCertURL, chainingMode, checkRevocation, revocationMethodOrder, null, null, null); } catch (IOException e) { throw new MOAHttpProtocolSocketFactoryException("Initialize SSL Context FAILED", e); } catch (GeneralSecurityException e) { throw new MOAHttpProtocolSocketFactoryException("Initialize SSL Context FAILED", e); } catch (SSLConfigurationException e) { throw new MOAHttpProtocolSocketFactoryException("SSL Configuration loading FAILED.", e); } catch (PKIException e) { throw new MOAHttpProtocolSocketFactoryException("Initialize SSL Context FAILED", e); } } /* (non-Javadoc) * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int) */ public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, UnknownHostException { return setSecurityRequirements(this.sslfactory.createSocket(host, port, localAddress, localPort)); } /* (non-Javadoc) * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int, org.apache.commons.httpclient.params.HttpConnectionParams) */ public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { return setSecurityRequirements(this.sslfactory.createSocket(host, port, localAddress, localPort)); } /* (non-Javadoc) * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int) */ public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return setSecurityRequirements(this.sslfactory.createSocket(host, port)); } /* (non-Javadoc) * @see org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory#createSocket(java.net.Socket, java.lang.String, int, boolean) */ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return setSecurityRequirements(this.sslfactory.createSocket(socket, host, port, autoClose)); } private Socket setSecurityRequirements(Socket socket) throws SSLException { if (socket instanceof SSLSocket) { SSLSocket sslSocket = (SSLSocket)socket; /*TODO * Set allowed ProtocolVersions into SSLSocket to support TLSv1.1 and TLSv1.2 in JAVA 7 * Therefore, we had do manually set the TLS1.2 protocol support into SSLParameters * from SSL socket. Maybe, there is an additional validation required if TLSv1.2 is * supported in principle by currently used JAVA version. */ // SSLParameters test = ((SSLSocket) socket).getSSLParameters(); // List enabledProtocols = Arrays.asList(test.getProtocols()); // if (enabledProtocols.contains(ProtocolVersion.TLS11.name)) { // // } // sslSocket.setSSLParameters(test); //verify Hostname verifyHostName(sslSocket); //set allowed SSL ciphers //sslSocket = setEnabledSslCiphers(sslSocket); return sslSocket; } return socket; } /** * Verify hostname against SSL server certificate * * @param sslSocket * @throws SSLPeerUnverifiedException */ private void verifyHostName(SSLSocket sslSocket) throws SSLException{ if (verifyHostName) { SSLSession session = sslSocket.getSession(); if ("SSL_NULL_WITH_NULL_NULL".equals(session.getCipherSuite())) { Logger.warn("SSL connection can NOT established."); throw new SSLException("SSL connection can NOT established."); } String hostName = session.getPeerHost(); Certificate[] certs = null; try { certs = session.getPeerCertificates(); if (certs == null || certs.length < 1) throw new SSLPeerUnverifiedException("No server certificates found!"); X509Certificate x509 = (X509Certificate)certs[0]; DefaultHostnameVerifier hostNameVerifier = new DefaultHostnameVerifier(); hostNameVerifier.verify(hostName, x509); } catch (SSLPeerUnverifiedException e) { Logger.error("Host:" + hostName + " sends no certificates for validation.", e); throw e; } catch (SSLException e) { Logger.error("Hostname validation FAILED:" + hostName + " validation ", e); //log certificates in case of Debug if (Logger.isDebugEnabled() && certs != null) { Logger.debug("Server certificate chain:"); for (int i = 0; i < certs.length; i++) { Logger.debug("X509Certificate[" + i + "]=" + certs[i]); } } throw e; } } } /** * Enable only a specific subset of TLS cipher suites * This subset can be set by 'https.cipherSuites' SystemProperty (z.B. -Dhttps.cipherSuites=...) * * @param sslSocket {@link SSLSocket} * @return {@link SSLSocket} with Ciphersuites */ private SSLSocket setEnabledSslCiphers(SSLSocket sslSocket) { /*TODO: * This implementation currently not work fine, because not all ciphers from * 'https.cipherSuites' SystemProperty had to be supported by current JAVA version * Add an validation step to check the allowed cipherSuites against the currently * supported cipher suites and only add the matching set of ciphers */ String systemProp = System.getProperty("https.cipherSuites"); if (MiscUtil.isNotEmpty(systemProp)) { try { List possibleCiphers = new ArrayList(); List supportedCiphers = Arrays.asList(sslSocket.getSupportedCipherSuites()); for (String el : systemProp.split(",")) { if (supportedCiphers.contains(el)) possibleCiphers.add(el); else Logger.debug("Ignore unsupported cipher: " + el); } sslSocket.setEnabledCipherSuites(possibleCiphers.toArray(new String[possibleCiphers.size()])); try { Logger.trace("Enabled SSL-Cipher: " + StringUtils.join(((SSLSocket) sslSocket).getEnabledCipherSuites(), ",")); } catch (Exception e) { Logger.error(e); } } catch (IllegalArgumentException e) { Logger.warn("Can not set allowed https.cipherSuites to httpClient. Use default set!"); } } return sslSocket; } }