KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > contrib > ssl > StrictSSLProtocolSocketFactory


1 /*
2  * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/contrib/org/apache/commons/httpclient/contrib/ssl/StrictSSLProtocolSocketFactory.java,v 1.5 2004/06/10 18:25:24 olegk Exp $
3  * $Revision$
4  * $Date$
5  *
6  * ====================================================================
7  *
8  * Copyright 1999-2004 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ====================================================================
22  *
23  * This software consists of voluntary contributions made by many
24  * individuals on behalf of the Apache Software Foundation. For more
25  * information on the Apache Software Foundation, please see
26  * <http://www.apache.org/>.
27  *
28  * [Additional notices, if required by prior licensing conditions]
29  *
30  * Alternatively, the contents of this file may be used under the
31  * terms of the GNU Lesser General Public License Version 2 or later
32  * (the "LGPL"), in which case the provisions of the LGPL are
33  * applicable instead of those above. See terms of LGPL at
34  * <http://www.gnu.org/copyleft/lesser.txt>.
35  * If you wish to allow use of your version of this file only under
36  * the terms of the LGPL and not to allow others to use your version
37  * of this file under the Apache Software License, indicate your
38  * decision by deleting the provisions above and replace them with
39  * the notice and other provisions required by the LGPL. If you do
40  * not delete the provisions above, a recipient may use your version
41  * of this file under either the Apache Software License or the LGPL.
42  */

43
44 package org.apache.commons.httpclient.contrib.ssl;
45
46 import java.io.IOException JavaDoc;
47 import java.net.InetAddress JavaDoc;
48 import java.net.InetSocketAddress JavaDoc;
49 import java.net.Socket JavaDoc;
50 import java.net.SocketAddress JavaDoc;
51 import java.net.UnknownHostException JavaDoc;
52
53 import javax.net.SocketFactory;
54 import javax.net.ssl.SSLPeerUnverifiedException;
55 import javax.net.ssl.SSLSession;
56 import javax.net.ssl.SSLSocket;
57 import javax.net.ssl.SSLSocketFactory;
58 import javax.security.cert.X509Certificate;
59
60 import org.apache.commons.httpclient.ConnectTimeoutException;
61 import org.apache.commons.httpclient.params.HttpConnectionParams;
62 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
63 import org.apache.commons.logging.Log;
64 import org.apache.commons.logging.LogFactory;
65
66 /**
67  * A <code>SecureProtocolSocketFactory</code> that uses JSSE to create
68  * SSL sockets. It will also support host name verification to help preventing
69  * man-in-the-middle attacks. Host name verification is turned <b>on</b> by
70  * default but one will be able to turn it off, which might be a useful feature
71  * during development. Host name verification will make sure the SSL sessions
72  * server host name matches with the the host name returned in the
73  * server certificates "Common Name" field of the "SubjectDN" entry.
74  *
75  * @author <a HREF="mailto:hauer@psicode.com">Sebastian Hauer</a>
76  * <p>
77  * DISCLAIMER: HttpClient developers DO NOT actively support this component.
78  * The component is provided as a reference material, which may be inappropriate
79  * for use without additional customization.
80  * </p>
81  */

82 public class StrictSSLProtocolSocketFactory
83     implements SecureProtocolSocketFactory {
84
85     /** Log object for this class. */
86     private static final Log LOG = LogFactory.getLog(StrictSSLProtocolSocketFactory.class);
87
88     /** Host name verify flag. */
89     private boolean verifyHostname = true;
90
91
92     /**
93      * Constructor for StrictSSLProtocolSocketFactory.
94      * @param verifyHostname The host name verification flag. If set to
95      * <code>true</code> the SSL sessions server host name will be compared
96      * to the host name returned in the server certificates "Common Name"
97      * field of the "SubjectDN" entry. If these names do not match a
98      * Exception is thrown to indicate this. Enabling host name verification
99      * will help to prevent from man-in-the-middle attacks. If set to
100      * <code>false</code> host name verification is turned off.
101      *
102      * Code sample:
103      *
104      * <blockquote>
105      * Protocol stricthttps = new Protocol(
106      * "https", new StrictSSLProtocolSocketFactory(true), 443);
107      *
108      * HttpClient client = new HttpClient();
109      * client.getHostConfiguration().setHost("localhost", 443, stricthttps);
110      * </blockquote>
111      *
112      */

113     public StrictSSLProtocolSocketFactory(boolean verifyHostname) {
114         super();
115         this.verifyHostname = verifyHostname;
116     }
117
118     /**
119      * Constructor for StrictSSLProtocolSocketFactory.
120      * Host name verification will be enabled by default.
121      */

122     public StrictSSLProtocolSocketFactory() {
123         super();
124     }
125
126     /**
127      * Set the host name verification flag.
128      *
129      * @param verifyHostname The host name verification flag. If set to
130      * <code>true</code> the SSL sessions server host name will be compared
131      * to the host name returned in the server certificates "Common Name"
132      * field of the "SubjectDN" entry. If these names do not match a
133      * Exception is thrown to indicate this. Enabling host name verification
134      * will help to prevent from man-in-the-middle attacks. If set to
135      * <code>false</code> host name verification is turned off.
136      */

137     public void setHostnameVerification(boolean verifyHostname) {
138         this.verifyHostname = verifyHostname;
139     }
140
141     /**
142      * Gets the status of the host name verification flag.
143      *
144      * @return Host name verification flag. Either <code>true</code> if host
145      * name verification is turned on, or <code>false</code> if host name
146      * verification is turned off.
147      */

148     public boolean getHostnameVerification() {
149         return verifyHostname;
150     }
151
152     
153     /**
154      * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
155      */

156     public Socket JavaDoc createSocket(String JavaDoc host, int port,
157                                InetAddress JavaDoc clientHost, int clientPort)
158         throws IOException JavaDoc, UnknownHostException JavaDoc {
159         SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
160         SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port,
161                                                           clientHost,
162                                                           clientPort);
163         verifyHostname(sslSocket);
164
165         return sslSocket;
166     }
167
168     /**
169      * Attempts to get a new socket connection to the given host within the given time limit.
170      * <p>
171      * This method employs several techniques to circumvent the limitations of older JREs that
172      * do not support connect timeout. When running in JRE 1.4 or above reflection is used to
173      * call Socket#connect(SocketAddress endpoint, int timeout) method. When executing in older
174      * JREs a controller thread is executed. The controller thread attempts to create a new socket
175      * within the given limit of time. If socket constructor does not return until the timeout
176      * expires, the controller terminates and throws an {@link ConnectTimeoutException}
177      * </p>
178      *
179      * @param host the host name/IP
180      * @param port the port on the host
181      * @param clientHost the local host name/IP to bind the socket to
182      * @param clientPort the port on the local machine
183      * @param params {@link HttpConnectionParams Http connection parameters}
184      *
185      * @return Socket a new socket
186      *
187      * @throws IOException if an I/O error occurs while creating the socket
188      * @throws UnknownHostException if the IP address of the host cannot be
189      * determined
190      */

191     public Socket JavaDoc createSocket(
192         final String JavaDoc host,
193         final int port,
194         final InetAddress JavaDoc localAddress,
195         final int localPort,
196         final HttpConnectionParams params
197     ) throws IOException JavaDoc, UnknownHostException JavaDoc, ConnectTimeoutException {
198         if (params == null) {
199             throw new IllegalArgumentException JavaDoc("Parameters may not be null");
200         }
201         int timeout = params.getConnectionTimeout();
202         Socket JavaDoc socket = null;
203         
204         SocketFactory socketfactory = SSLSocketFactory.getDefault();
205         if (timeout == 0) {
206             socket = socketfactory.createSocket(host, port, localAddress, localPort);
207         } else {
208             socket = socketfactory.createSocket();
209             SocketAddress JavaDoc localaddr = new InetSocketAddress JavaDoc(localAddress, localPort);
210             SocketAddress JavaDoc remoteaddr = new InetSocketAddress JavaDoc(host, port);
211             socket.bind(localaddr);
212             socket.connect(remoteaddr, timeout);
213         }
214         verifyHostname((SSLSocket)socket);
215         return socket;
216     }
217
218     /**
219      * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
220      */

221     public Socket JavaDoc createSocket(String JavaDoc host, int port)
222         throws IOException JavaDoc, UnknownHostException JavaDoc {
223         SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
224         SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port);
225         verifyHostname(sslSocket);
226
227         return sslSocket;
228     }
229
230     /**
231      * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
232      */

233     public Socket JavaDoc createSocket(Socket JavaDoc socket, String JavaDoc host, int port,
234                                boolean autoClose)
235         throws IOException JavaDoc, UnknownHostException JavaDoc {
236         SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
237         SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host,
238                                                           port, autoClose);
239         verifyHostname(sslSocket);
240
241         return sslSocket;
242     }
243
244
245     /**
246      * Describe <code>verifyHostname</code> method here.
247      *
248      * @param socket a <code>SSLSocket</code> value
249      * @exception SSLPeerUnverifiedException If there are problems obtaining
250      * the server certificates from the SSL session, or the server host name
251      * does not match with the "Common Name" in the server certificates
252      * SubjectDN.
253      * @exception UnknownHostException If we are not able to resolve
254      * the SSL sessions returned server host name.
255      */

256     private void verifyHostname(SSLSocket socket)
257         throws SSLPeerUnverifiedException, UnknownHostException JavaDoc {
258         if (! verifyHostname)
259             return;
260
261         SSLSession session = socket.getSession();
262         String JavaDoc hostname = session.getPeerHost();
263         try {
264             InetAddress JavaDoc addr = InetAddress.getByName(hostname);
265         } catch (UnknownHostException JavaDoc uhe) {
266             throw new UnknownHostException JavaDoc("Could not resolve SSL sessions "
267                                            + "server hostname: " + hostname);
268         }
269         
270         X509Certificate[] certs = session.getPeerCertificateChain();
271         if (certs == null || certs.length == 0)
272             throw new SSLPeerUnverifiedException("No server certificates found!");
273         
274         //get the servers DN in its string representation
275
String JavaDoc dn = certs[0].getSubjectDN().getName();
276
277         //might be useful to print out all certificates we receive from the
278
//server, in case one has to debug a problem with the installed certs.
279
if (LOG.isDebugEnabled()) {
280             LOG.debug("Server certificate chain:");
281             for (int i = 0; i < certs.length; i++) {
282                 LOG.debug("X509Certificate[" + i + "]=" + certs[i]);
283             }
284         }
285         //get the common name from the first cert
286
String JavaDoc cn = getCN(dn);
287         if (hostname.equalsIgnoreCase(cn)) {
288             if (LOG.isDebugEnabled()) {
289                 LOG.debug("Target hostname valid: " + cn);
290             }
291         } else {
292             throw new SSLPeerUnverifiedException(
293                 "HTTPS hostname invalid: expected '" + hostname + "', received '" + cn + "'");
294         }
295     }
296
297
298     /**
299      * Parses a X.500 distinguished name for the value of the
300      * "Common Name" field.
301      * This is done a bit sloppy right now and should probably be done a bit
302      * more according to <code>RFC 2253</code>.
303      *
304      * @param dn a X.500 distinguished name.
305      * @return the value of the "Common Name" field.
306      */

307     private String JavaDoc getCN(String JavaDoc dn) {
308         int i = 0;
309         i = dn.indexOf("CN=");
310         if (i == -1) {
311             return null;
312         }
313         //get the remaining DN without CN=
314
dn = dn.substring(i + 3);
315         // System.out.println("dn=" + dn);
316
char[] dncs = dn.toCharArray();
317         for (i = 0; i < dncs.length; i++) {
318             if (dncs[i] == ',' && i > 0 && dncs[i - 1] != '\\') {
319                 break;
320             }
321         }
322         return dn.substring(0, i);
323     }
324     
325     public boolean equals(Object JavaDoc obj) {
326         if ((obj != null) && obj.getClass().equals(StrictSSLProtocolSocketFactory.class)) {
327             return ((StrictSSLProtocolSocketFactory) obj).getHostnameVerification()
328                 == this.verifyHostname;
329         } else {
330             return false;
331         }
332     }
333
334     public int hashCode() {
335         return StrictSSLProtocolSocketFactory.class.hashCode();
336     }
337
338 }
Popular Tags