/* Copyright (c) 2001-2010, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb.server; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.security.Principal; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.security.cert.X509Certificate; import org.hsqldb.error.Error; import org.hsqldb.error.ErrorCode; import org.hsqldb.lib.StringConverter; /** * The default secure socket factory implementation. * * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net) * @author Blaine Simpson (blaine dot simpson at admc dot com) * * @version 1.7.2 * @since 1.7.2 */ public final class HsqlSocketFactorySecure extends HsqlSocketFactory implements HandshakeCompletedListener { // --------------------------------- members ----------------------------------- /** The underlying socket factory implementation. */ protected Object socketFactory; /** The underlying server socket factory implementation. */ protected Object serverSocketFactory; /** * Monitor object to guard against conncurrent modification * of the underlying socket factory implementation member. */ protected final Object socket_factory_mutex = new Object(); /** * Monitor object to guard against concurrent modification of * the underlying server socket factory implementation member. */ protected final Object server_socket_factory_mutex = new Object(); // ------------------------------ constructors --------------------------------- /** * External construction disabled. New factory instances are retreived * through the newHsqlSocketFactory method instead. */ protected HsqlSocketFactorySecure() throws Exception { super(); Provider p; String cls; if (Security.getProvider("SunJSSE") == null) { try { p = (Provider) Class.forName( "com.sun.net.ssl.internal.ssl.Provider").newInstance(); Security.addProvider(p); } catch (Exception e) {} } } // ----------------------------- subclass overrides ---------------------------- public void configureSocket(Socket socket) { SSLSocket s; super.configureSocket(socket); s = (SSLSocket) socket; s.addHandshakeCompletedListener(this); } /** * Creates a secure server socket bound to the specified port. * The socket is configured with the socket options * given to this factory. * * @return the secure ServerSocket * @param port the port to which to bind the secure ServerSocket * @throws Exception if a network or security provider error occurs */ public ServerSocket createServerSocket(int port) throws Exception { SSLServerSocket ss; ss = (SSLServerSocket) getServerSocketFactoryImpl() .createServerSocket(port); if (Error.TRACESYSTEMOUT) { Error.printSystemOut("[" + this + "]: createServerSocket()"); Error.printSystemOut("capabilities for " + ss + ":"); Error.printSystemOut("----------------------------"); dump("supported cipher suites", ss.getSupportedCipherSuites()); dump("enabled cipher suites", ss.getEnabledCipherSuites()); } return ss; } /** * Creates a secure server socket bound to the specified port. * The socket is configured with the socket options * given to this factory. * * @return the secure ServerSocket * @param port the port to which to bind the secure ServerSocket * @throws Exception if a network or security provider error occurs */ public ServerSocket createServerSocket(int port, String address) throws Exception { SSLServerSocket ss; InetAddress addr; addr = InetAddress.getByName(address); ss = (SSLServerSocket) getServerSocketFactoryImpl() .createServerSocket(port, 128, addr); if (Error.TRACE) { Error.printSystemOut("[" + this + "]: createServerSocket()"); Error.printSystemOut("capabilities for " + ss + ":"); Error.printSystemOut("----------------------------"); dump("supported cipher suites", ss.getSupportedCipherSuites()); dump("enabled cipher suites", ss.getEnabledCipherSuites()); } return ss; } private static void dump(String title, String[] as) { Error.printSystemOut(title); Error.printSystemOut("----------------------------"); for (int i = 0; i < as.length; i++) { Error.printSystemOut(String.valueOf(as[i])); } Error.printSystemOut("----------------------------"); } /** * Creates a secure Socket and connects it to the specified remote host * at the specified remote port. This socket is configured using the * socket options established for this factory. * * @return the socket * @param host the server host * @param port the server port * @throws Exception if a network or security provider error occurs */ public Socket createSocket(String host, int port) throws Exception { SSLSocket socket; socket = (SSLSocket) getSocketFactoryImpl().createSocket(host, port); socket.addHandshakeCompletedListener(this); socket.startHandshake(); // unsaved@users // For https protocol, the protocol handler should do this verification // (Sun's implementation does), but if we do not use the Protocol // handler (which is only available in Java >= 1.4), then we need to do // the verification: hostname == cert CN // // boucherb@users 20030503: // CHEKME/TODO: // // Stricter verify? Either require SunJSSE (assume its trust manager properly // verifies whole chain), or implement our own TrustManager layer? // // What about v1/v3 and signing checks (re: man-in-the-middle attack), // CRL check, basic constraints? notBefore? notAfter? // // Reference: http://www.securitytracker.com/alerts/2002/Aug/1005030.html // // That is, we can't guarantee that installed/prefered provider trust manager // implementations verify the whole chain properly and there are still // v1 certs out there (i.e. have no basic constraints, etc.), meaning that // we should check for and reject any intermediate certs that are not v3+ // (cannot be checked for basic constraints). Only root and intermediate // certs found in the trust store should be allowed to be v1 (since we must // be trusing them for them to be there). All other intermediate signers, // however, should be required to be v3+, otherwise anybody with any kind // of cert issued somehow via a trust chain from the root can pose as an // intermediate signing CA and hence leave things open to man-in-the-middle // style attack. Also, we should really check CRLs, just in case // it turns out that trust chain has been breached and thus issuer has revoked // on some cert(s). Of course, this really begs the question, as it is not // guaranteed that all CAs in trust store have valid, working CRL URL // // So what to do? // // Maybe best to leave this all up to DBA? verify(host, socket.getSession()); return socket; } /** * Retrieves whether this factory produces secure sockets. * * @return true iff this factory creates secure sockets */ public boolean isSecure() { return true; } // ----------------------- internal implementation ----------------------------- /** * Retrieves the underlying javax.net.ssl.SSLServerSocketFactory. * * @throws Exception if there is a problem retrieving the * underlying factory * @return the underlying javax.net.ssl.SSLServerSocketFactory */ protected SSLServerSocketFactory getServerSocketFactoryImpl() throws Exception { Object factory; synchronized (server_socket_factory_mutex) { factory = serverSocketFactory; if (factory == null) { factory = SSLServerSocketFactory.getDefault(); serverSocketFactory = factory; } } return (SSLServerSocketFactory) factory; } /** * Retrieves the underlying javax.net.ssl.SSLSocketFactory. * * @throws Exception if there is a problem retrieving the * underlying factory * @return the underlying javax.net.ssl.SSLSocketFactory */ protected SSLSocketFactory getSocketFactoryImpl() throws Exception { Object factory; synchronized (socket_factory_mutex) { factory = socketFactory; if (factory == null) { factory = SSLSocketFactory.getDefault(); socketFactory = factory; } } return (SSLSocketFactory) factory; } /** * Verifyies the certificate chain presented by the server to which * a secure Socket has just connected. Specifically, the provided host * name is checked against the Common Name of the server certificate; * additional checks may or may not be performed. * * @param host the requested host name * @param session SSLSession used on the connection to host * @throws Exception if the certificate chain cannot be verified */ protected void verify(String host, SSLSession session) throws Exception { X509Certificate[] chain; X509Certificate certificate; Principal principal; PublicKey publicKey; String DN; String CN; int start; int end; String emsg; chain = session.getPeerCertificateChain(); certificate = chain[0]; principal = certificate.getSubjectDN(); DN = String.valueOf(principal); start = DN.indexOf("CN="); if (start < 0) { throw new UnknownHostException( Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_1)); } start += 3; end = DN.indexOf(',', start); CN = DN.substring(start, (end > -1) ? end : DN.length()); if (CN.length() < 1) { throw new UnknownHostException( Error.getMessage(ErrorCode.M_SERVER_SECURE_VERIFY_2)); } if (!CN.equalsIgnoreCase(host)) { // TLS_HOSTNAME_MISMATCH throw new UnknownHostException( Error.getMessage( ErrorCode.M_SERVER_SECURE_VERIFY_3, 0, new Object[] { CN, host })); } } public void handshakeCompleted(HandshakeCompletedEvent evt) { SSLSession session; String sessionId; SSLSocket socket; if (Error.TRACE) { socket = evt.getSocket(); session = evt.getSession(); Error.printSystemOut("SSL handshake completed:"); Error.printSystemOut( "------------------------------------------------"); Error.printSystemOut("socket: : " + socket); Error.printSystemOut("cipher suite : " + session.getCipherSuite()); sessionId = StringConverter.byteArrayToHexString(session.getId()); Error.printSystemOut("session id : " + sessionId); Error.printSystemOut( "------------------------------------------------"); } } }