/[Apache-SVN]/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/StrictSSLProtocolSocketFactory.java
ViewVC logotype

Contents of /httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/StrictSSLProtocolSocketFactory.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 608014 - (show annotations)
Wed Jan 2 05:48:53 2008 UTC (22 months, 3 weeks ago) by rolandw
File size: 13681 byte(s)
replaced CVS keyword Header with SVN keyword HeadURL
1 /*
2 * $HeadURL$
3 * $Revision$
4 * $Date$
5 *
6 * ====================================================================
7 *
8 * Licensed to the Apache Software Foundation (ASF) under one or more
9 * contributor license agreements. See the NOTICE file distributed with
10 * this work for additional information regarding copyright ownership.
11 * The ASF licenses this file to You under the Apache License, Version 2.0
12 * (the "License"); you may not use this file except in compliance with
13 * the License. You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation. For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 * [Additional notices, if required by prior licensing conditions]
30 *
31 * Alternatively, the contents of this file may be used under the
32 * terms of the GNU Lesser General Public License Version 2 or later
33 * (the "LGPL"), in which case the provisions of the LGPL are
34 * applicable instead of those above. See terms of LGPL at
35 * <http://www.gnu.org/copyleft/lesser.txt>.
36 * If you wish to allow use of your version of this file only under
37 * the terms of the LGPL and not to allow others to use your version
38 * of this file under the Apache Software License, indicate your
39 * decision by deleting the provisions above and replace them with
40 * the notice and other provisions required by the LGPL. If you do
41 * not delete the provisions above, a recipient may use your version
42 * of this file under either the Apache Software License or the LGPL.
43 */
44
45 package org.apache.commons.httpclient.contrib.ssl;
46
47 import java.io.IOException;
48 import java.net.InetAddress;
49 import java.net.InetSocketAddress;
50 import java.net.Socket;
51 import java.net.SocketAddress;
52 import java.net.UnknownHostException;
53
54 import javax.net.SocketFactory;
55 import javax.net.ssl.SSLPeerUnverifiedException;
56 import javax.net.ssl.SSLSession;
57 import javax.net.ssl.SSLSocket;
58 import javax.net.ssl.SSLSocketFactory;
59 import javax.security.cert.X509Certificate;
60
61 import org.apache.commons.httpclient.ConnectTimeoutException;
62 import org.apache.commons.httpclient.params.HttpConnectionParams;
63 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
64 import org.apache.commons.logging.Log;
65 import org.apache.commons.logging.LogFactory;
66
67 /**
68 * A <code>SecureProtocolSocketFactory</code> that uses JSSE to create
69 * SSL sockets. It will also support host name verification to help preventing
70 * man-in-the-middle attacks. Host name verification is turned <b>on</b> by
71 * default but one will be able to turn it off, which might be a useful feature
72 * during development. Host name verification will make sure the SSL sessions
73 * server host name matches with the the host name returned in the
74 * server certificates "Common Name" field of the "SubjectDN" entry.
75 *
76 * @author <a href="mailto:hauer@psicode.com">Sebastian Hauer</a>
77 * <p>
78 * DISCLAIMER: HttpClient developers DO NOT actively support this component.
79 * The component is provided as a reference material, which may be inappropriate
80 * for use without additional customization.
81 * </p>
82 */
83 public class StrictSSLProtocolSocketFactory
84 implements SecureProtocolSocketFactory {
85
86 /** Log object for this class. */
87 private static final Log LOG = LogFactory.getLog(StrictSSLProtocolSocketFactory.class);
88
89 /** Host name verify flag. */
90 private boolean verifyHostname = true;
91
92
93 /**
94 * Constructor for StrictSSLProtocolSocketFactory.
95 * @param verifyHostname The host name verification flag. If set to
96 * <code>true</code> the SSL sessions server host name will be compared
97 * to the host name returned in the server certificates "Common Name"
98 * field of the "SubjectDN" entry. If these names do not match a
99 * Exception is thrown to indicate this. Enabling host name verification
100 * will help to prevent from man-in-the-middle attacks. If set to
101 * <code>false</code> host name verification is turned off.
102 *
103 * Code sample:
104 *
105 * <blockquote>
106 * Protocol stricthttps = new Protocol(
107 * "https", new StrictSSLProtocolSocketFactory(true), 443);
108 *
109 * HttpClient client = new HttpClient();
110 * client.getHostConfiguration().setHost("localhost", 443, stricthttps);
111 * </blockquote>
112 *
113 */
114 public StrictSSLProtocolSocketFactory(boolean verifyHostname) {
115 super();
116 this.verifyHostname = verifyHostname;
117 }
118
119 /**
120 * Constructor for StrictSSLProtocolSocketFactory.
121 * Host name verification will be enabled by default.
122 */
123 public StrictSSLProtocolSocketFactory() {
124 super();
125 }
126
127 /**
128 * Set the host name verification flag.
129 *
130 * @param verifyHostname The host name verification flag. If set to
131 * <code>true</code> the SSL sessions server host name will be compared
132 * to the host name returned in the server certificates "Common Name"
133 * field of the "SubjectDN" entry. If these names do not match a
134 * Exception is thrown to indicate this. Enabling host name verification
135 * will help to prevent from man-in-the-middle attacks. If set to
136 * <code>false</code> host name verification is turned off.
137 */
138 public void setHostnameVerification(boolean verifyHostname) {
139 this.verifyHostname = verifyHostname;
140 }
141
142 /**
143 * Gets the status of the host name verification flag.
144 *
145 * @return Host name verification flag. Either <code>true</code> if host
146 * name verification is turned on, or <code>false</code> if host name
147 * verification is turned off.
148 */
149 public boolean getHostnameVerification() {
150 return verifyHostname;
151 }
152
153
154 /**
155 * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
156 */
157 public Socket createSocket(String host, int port,
158 InetAddress clientHost, int clientPort)
159 throws IOException, UnknownHostException {
160 SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
161 SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port,
162 clientHost,
163 clientPort);
164 verifyHostname(sslSocket);
165
166 return sslSocket;
167 }
168
169 /**
170 * Attempts to get a new socket connection to the given host within the given time limit.
171 * <p>
172 * This method employs several techniques to circumvent the limitations of older JREs that
173 * do not support connect timeout. When running in JRE 1.4 or above reflection is used to
174 * call Socket#connect(SocketAddress endpoint, int timeout) method. When executing in older
175 * JREs a controller thread is executed. The controller thread attempts to create a new socket
176 * within the given limit of time. If socket constructor does not return until the timeout
177 * expires, the controller terminates and throws an {@link ConnectTimeoutException}
178 * </p>
179 *
180 * @param host the host name/IP
181 * @param port the port on the host
182 * @param clientHost the local host name/IP to bind the socket to
183 * @param clientPort the port on the local machine
184 * @param params {@link HttpConnectionParams Http connection parameters}
185 *
186 * @return Socket a new socket
187 *
188 * @throws IOException if an I/O error occurs while creating the socket
189 * @throws UnknownHostException if the IP address of the host cannot be
190 * determined
191 */
192 public Socket createSocket(
193 final String host,
194 final int port,
195 final InetAddress localAddress,
196 final int localPort,
197 final HttpConnectionParams params
198 ) throws IOException, UnknownHostException, ConnectTimeoutException {
199 if (params == null) {
200 throw new IllegalArgumentException("Parameters may not be null");
201 }
202 int timeout = params.getConnectionTimeout();
203 Socket socket = null;
204
205 SocketFactory socketfactory = SSLSocketFactory.getDefault();
206 if (timeout == 0) {
207 socket = socketfactory.createSocket(host, port, localAddress, localPort);
208 } else {
209 socket = socketfactory.createSocket();
210 SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
211 SocketAddress remoteaddr = new InetSocketAddress(host, port);
212 socket.bind(localaddr);
213 socket.connect(remoteaddr, timeout);
214 }
215 verifyHostname((SSLSocket)socket);
216 return socket;
217 }
218
219 /**
220 * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
221 */
222 public Socket createSocket(String host, int port)
223 throws IOException, UnknownHostException {
224 SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
225 SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port);
226 verifyHostname(sslSocket);
227
228 return sslSocket;
229 }
230
231 /**
232 * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
233 */
234 public Socket createSocket(Socket socket, String host, int port,
235 boolean autoClose)
236 throws IOException, UnknownHostException {
237 SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
238 SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host,
239 port, autoClose);
240 verifyHostname(sslSocket);
241
242 return sslSocket;
243 }
244
245
246 /**
247 * Describe <code>verifyHostname</code> method here.
248 *
249 * @param socket a <code>SSLSocket</code> value
250 * @exception SSLPeerUnverifiedException If there are problems obtaining
251 * the server certificates from the SSL session, or the server host name
252 * does not match with the "Common Name" in the server certificates
253 * SubjectDN.
254 * @exception UnknownHostException If we are not able to resolve
255 * the SSL sessions returned server host name.
256 */
257 private void verifyHostname(SSLSocket socket)
258 throws SSLPeerUnverifiedException, UnknownHostException {
259 if (! verifyHostname)
260 return;
261
262 SSLSession session = socket.getSession();
263 String hostname = session.getPeerHost();
264 try {
265 InetAddress addr = InetAddress.getByName(hostname);
266 } catch (UnknownHostException uhe) {
267 throw new UnknownHostException("Could not resolve SSL sessions "
268 + "server hostname: " + hostname);
269 }
270
271 X509Certificate[] certs = session.getPeerCertificateChain();
272 if (certs == null || certs.length == 0)
273 throw new SSLPeerUnverifiedException("No server certificates found!");
274
275 //get the servers DN in its string representation
276 String dn = certs[0].getSubjectDN().getName();
277
278 //might be useful to print out all certificates we receive from the
279 //server, in case one has to debug a problem with the installed certs.
280 if (LOG.isDebugEnabled()) {
281 LOG.debug("Server certificate chain:");
282 for (int i = 0; i < certs.length; i++) {
283 LOG.debug("X509Certificate[" + i + "]=" + certs[i]);
284 }
285 }
286 //get the common name from the first cert
287 String cn = getCN(dn);
288 if (hostname.equalsIgnoreCase(cn)) {
289 if (LOG.isDebugEnabled()) {
290 LOG.debug("Target hostname valid: " + cn);
291 }
292 } else {
293 throw new SSLPeerUnverifiedException(
294 "HTTPS hostname invalid: expected '" + hostname + "', received '" + cn + "'");
295 }
296 }
297
298
299 /**
300 * Parses a X.500 distinguished name for the value of the
301 * "Common Name" field.
302 * This is done a bit sloppy right now and should probably be done a bit
303 * more according to <code>RFC 2253</code>.
304 *
305 * @param dn a X.500 distinguished name.
306 * @return the value of the "Common Name" field.
307 */
308 private String getCN(String dn) {
309 int i = 0;
310 i = dn.indexOf("CN=");
311 if (i == -1) {
312 return null;
313 }
314 //get the remaining DN without CN=
315 dn = dn.substring(i + 3);
316 // System.out.println("dn=" + dn);
317 char[] dncs = dn.toCharArray();
318 for (i = 0; i < dncs.length; i++) {
319 if (dncs[i] == ',' && i > 0 && dncs[i - 1] != '\\') {
320 break;
321 }
322 }
323 return dn.substring(0, i);
324 }
325
326 public boolean equals(Object obj) {
327 if ((obj != null) && obj.getClass().equals(StrictSSLProtocolSocketFactory.class)) {
328 return ((StrictSSLProtocolSocketFactory) obj).getHostnameVerification()
329 == this.verifyHostname;
330 } else {
331 return false;
332 }
333 }
334
335 public int hashCode() {
336 return StrictSSLProtocolSocketFactory.class.hashCode();
337 }
338
339 }

Properties

Name Value
svn:eol-style native
svn:keywords Date Author Id Revision HeadURL

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2