001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import java.net.InetAddress;
034: import java.net.ServerSocket;
035: import java.net.Socket;
036: import java.net.UnknownHostException;
037: import java.security.Principal;
038: import java.security.Provider;
039: import java.security.PublicKey;
040: import java.security.Security;
041:
042: import javax.net.ssl.HandshakeCompletedEvent;
043: import javax.net.ssl.HandshakeCompletedListener;
044: import javax.net.ssl.SSLServerSocket;
045: import javax.net.ssl.SSLServerSocketFactory;
046: import javax.net.ssl.SSLSession;
047: import javax.net.ssl.SSLSocket;
048: import javax.net.ssl.SSLSocketFactory;
049: import javax.security.cert.X509Certificate;
050:
051: import org.hsqldb.lib.StringConverter;
052:
053: /**
054: * The default secure socket factory implementation.
055: *
056: * @author unsaved@users
057: * @author boucherb@users
058: * @version 1.7.2
059: * @since 1.7.2
060: */
061: public final class HsqlSocketFactorySecure extends HsqlSocketFactory
062: implements HandshakeCompletedListener {
063:
064: // --------------------------------- members -----------------------------------
065:
066: /** The underlying socket factory implementation. */
067: protected Object socketFactory;
068:
069: /** The underlying server socket factory implementation. */
070: protected Object serverSocketFactory;
071:
072: /**
073: * Monitor object to guard against conncurrent modification
074: * of the underlying socket factory implementation member.
075: */
076: protected final Object socket_factory_mutex = new Object();
077:
078: /**
079: * Monitor object to guard against concurrent modification of
080: * the underlying server socket factory implementation member.
081: */
082: protected final Object server_socket_factory_mutex = new Object();
083:
084: // ------------------------------ constructors ---------------------------------
085:
086: /**
087: * External construction disabled. New factory instances are retreived
088: * through the newHsqlSocketFactory method instead.
089: */
090: protected HsqlSocketFactorySecure() throws Exception {
091:
092: super ();
093:
094: Provider p;
095: String cls;
096:
097: if (Security.getProvider("SunJSSE") == null) {
098: try {
099: p = (Provider) Class.forName(
100: "com.sun.net.ssl.internal.ssl.Provider")
101: .newInstance();
102:
103: Security.addProvider(p);
104: } catch (Exception e) {
105: }
106: }
107: }
108:
109: // ----------------------------- subclass overrides ----------------------------
110: public void configureSocket(Socket socket) {
111:
112: SSLSocket s;
113:
114: super .configureSocket(socket);
115:
116: s = (SSLSocket) socket;
117:
118: s.addHandshakeCompletedListener(this );
119: }
120:
121: /**
122: * Creates a secure server socket bound to the specified port.
123: * The socket is configured with the socket options
124: * given to this factory.
125: *
126: * @return the secure ServerSocket
127: * @param port the port to which to bind the secure ServerSocket
128: * @throws Exception if a network or security provider error occurs
129: */
130: public ServerSocket createServerSocket(int port) throws Exception {
131:
132: SSLServerSocket ss;
133:
134: ss = (SSLServerSocket) getServerSocketFactoryImpl()
135: .createServerSocket(port);
136:
137: if (Trace.TRACE) {
138: Trace
139: .printSystemOut("[" + this
140: + "]: createServerSocket()");
141: Trace.printSystemOut("capabilities for " + ss + ":");
142: Trace.printSystemOut("----------------------------");
143: dump("supported cipher suites", ss
144: .getSupportedCipherSuites());
145: dump("enabled cipher suites", ss.getEnabledCipherSuites());
146: }
147:
148: return ss;
149: }
150:
151: /**
152: * Creates a secure server socket bound to the specified port.
153: * The socket is configured with the socket options
154: * given to this factory.
155: *
156: * @return the secure ServerSocket
157: * @param port the port to which to bind the secure ServerSocket
158: * @throws Exception if a network or security provider error occurs
159: */
160: public ServerSocket createServerSocket(int port, String address)
161: throws Exception {
162:
163: SSLServerSocket ss;
164: InetAddress addr;
165:
166: addr = InetAddress.getByName(address);
167: ss = (SSLServerSocket) getServerSocketFactoryImpl()
168: .createServerSocket(port, 128, addr);
169:
170: if (Trace.TRACE) {
171: Trace
172: .printSystemOut("[" + this
173: + "]: createServerSocket()");
174: Trace.printSystemOut("capabilities for " + ss + ":");
175: Trace.printSystemOut("----------------------------");
176: dump("supported cipher suites", ss
177: .getSupportedCipherSuites());
178: dump("enabled cipher suites", ss.getEnabledCipherSuites());
179: }
180:
181: return ss;
182: }
183:
184: private static void dump(String title, String[] as) {
185:
186: Trace.printSystemOut(title);
187: Trace.printSystemOut("----------------------------");
188:
189: for (int i = 0; i < as.length; i++) {
190: Trace.printSystemOut(String.valueOf(as[i]));
191: }
192:
193: Trace.printSystemOut("----------------------------");
194: }
195:
196: /**
197: * Creates a secure Socket and connects it to the specified remote host
198: * at the specified remote port. This socket is configured using the
199: * socket options established for this factory.
200: *
201: * @return the socket
202: * @param host the server host
203: * @param port the server port
204: * @throws Exception if a network or security provider error occurs
205: */
206: public Socket createSocket(String host, int port) throws Exception {
207:
208: SSLSocket socket;
209:
210: socket = (SSLSocket) getSocketFactoryImpl().createSocket(host,
211: port);
212:
213: socket.addHandshakeCompletedListener(this );
214: socket.startHandshake();
215:
216: // unsaved@users
217: // For https protocol, the protocol handler should do this verification
218: // (Sun's implementation does), but if we do not use the Protocol
219: // handler (which is only available in Java >= 1.4), then we need to do
220: // the verification: hostname == cert CN
221: //
222: // boucherb@users 20030503:
223: // CHEKME/TODO:
224: //
225: // Stricter verify? Either require SunJSSE (assume its trust manager properly
226: // verifies whole chain), or implement our own TrustManager layer?
227: //
228: // What about v1/v3 and signing checks (re: man-in-the-middle attack),
229: // CRL check, basic constraints? notBefore? notAfter?
230: //
231: // Reference: http://www.securitytracker.com/alerts/2002/Aug/1005030.html
232: //
233: // That is, we can't guarantee that installed/prefered provider trust manager
234: // implementations verify the whole chain properly and there are still
235: // v1 certs out there (i.e. have no basic constraints, etc.), meaning that
236: // we should check for and reject any intermediate certs that are not v3+
237: // (cannot be checked for basic constraints). Only root and intermediate
238: // certs found in the trust store should be allowed to be v1 (since we must
239: // be trusing them for them to be there). All other intermediate signers,
240: // however, should be required to be v3+, otherwise anybody with any kind
241: // of cert issued somehow via a trust chain from the root can pose as an
242: // intermediate signing CA and hence leave things open to man-in-the-middle
243: // style attack. Also, we should really check CRLs, just in case
244: // it turns out that trust chain has been breached and thus issuer has revoked
245: // on some cert(s). Of course, this really begs the question, as it is not
246: // guaranteed that all CAs in trust store have valid, working CRL URL
247: //
248: // So what to do?
249: //
250: // Maybe best to leave this all up to DBA?
251: verify(host, socket.getSession());
252:
253: return socket;
254: }
255:
256: /**
257: * Retrieves whether this factory produces secure sockets.
258: *
259: * @return true iff this factory creates secure sockets
260: */
261: public boolean isSecure() {
262: return true;
263: }
264:
265: // ----------------------- internal implementation -----------------------------
266:
267: /**
268: * Retrieves the underlying javax.net.ssl.SSLServerSocketFactory.
269: *
270: * @throws Exception if there is a problem retrieving the
271: * underlying factory
272: * @return the underlying javax.net.ssl.SSLServerSocketFactory
273: */
274: protected SSLServerSocketFactory getServerSocketFactoryImpl()
275: throws Exception {
276:
277: Object factory;
278:
279: synchronized (server_socket_factory_mutex) {
280: factory = serverSocketFactory;
281:
282: if (factory == null) {
283: factory = SSLServerSocketFactory.getDefault();
284: serverSocketFactory = factory;
285: }
286: }
287:
288: return (SSLServerSocketFactory) factory;
289: }
290:
291: /**
292: * Retrieves the underlying javax.net.ssl.SSLSocketFactory.
293: *
294: * @throws Exception if there is a problem retrieving the
295: * underlying factory
296: * @return the underlying javax.net.ssl.SSLSocketFactory
297: */
298: protected SSLSocketFactory getSocketFactoryImpl() throws Exception {
299:
300: Object factory;
301:
302: synchronized (socket_factory_mutex) {
303: factory = socketFactory;
304:
305: if (factory == null) {
306: factory = SSLSocketFactory.getDefault();
307: socketFactory = factory;
308: }
309: }
310:
311: return (SSLSocketFactory) factory;
312: }
313:
314: /**
315: * Verifyies the certificate chain presented by the server to which
316: * a secure Socket has just connected. Specifically, the provided host
317: * name is checked against the Common Name of the server certificate;
318: * additional checks may or may not be performed.
319: *
320: * @param host the requested host name
321: * @param session SSLSession used on the connection to host
322: * @throws Exception if the certificate chain cannot be verified
323: */
324: protected void verify(String host, SSLSession session)
325: throws Exception {
326:
327: X509Certificate[] chain;
328: X509Certificate certificate;
329: Principal principal;
330: PublicKey publicKey;
331: String DN;
332: String CN;
333: int start;
334: int end;
335: String emsg;
336:
337: chain = session.getPeerCertificateChain();
338: certificate = chain[0];
339: principal = certificate.getSubjectDN();
340: DN = String.valueOf(principal);
341: start = DN.indexOf("CN=");
342:
343: if (start < 0) {
344: throw new UnknownHostException(Trace
345: .getMessage(Trace.HsqlSocketFactorySecure_verify));
346: }
347:
348: start += 3;
349: end = DN.indexOf(',', start);
350: CN = DN.substring(start, (end > -1) ? end : DN.length());
351:
352: if (CN.length() < 1) {
353: throw new UnknownHostException(Trace
354: .getMessage(Trace.HsqlSocketFactorySecure_verify2));
355: }
356:
357: if (!CN.equalsIgnoreCase(host)) {
358:
359: // TLS_HOSTNAME_MISMATCH
360: throw new UnknownHostException(Trace.getMessage(
361: Trace.HsqlSocketFactorySecure_verify3, true,
362: new Object[] { CN, host }));
363: }
364: }
365:
366: public void handshakeCompleted(HandshakeCompletedEvent evt) {
367:
368: SSLSession session;
369: String sessionId;
370: SSLSocket socket;
371:
372: if (Trace.TRACE) {
373: socket = evt.getSocket();
374: session = evt.getSession();
375:
376: Trace.printSystemOut("SSL handshake completed:");
377: Trace
378: .printSystemOut("------------------------------------------------");
379: Trace.printSystemOut("socket: : " + socket);
380: Trace.printSystemOut("cipher suite : "
381: + session.getCipherSuite());
382:
383: sessionId = StringConverter.byteToHex(session.getId());
384:
385: Trace.printSystemOut("session id : " + sessionId);
386: Trace
387: .printSystemOut("------------------------------------------------");
388: }
389: }
390: }
|