View Javadoc
1   /*
2    * Copyright (c) 2002-2021, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.ctv.util;
35  
36  import java.io.IOException;
37  import java.net.InetAddress;
38  import java.net.InetSocketAddress;
39  import java.net.Socket;
40  import java.net.UnknownHostException;
41  import java.security.cert.X509Certificate;
42  import java.util.ArrayList;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.StringTokenizer;
46  
47  import javax.net.ssl.SSLContext;
48  import javax.net.ssl.SSLPeerUnverifiedException;
49  import javax.net.ssl.SSLSession;
50  import javax.net.ssl.SSLSocket;
51  import javax.net.ssl.SSLSocketFactory;
52  import javax.security.auth.x500.X500Principal;
53  
54  import org.apache.commons.httpclient.ConnectTimeoutException;
55  import org.apache.commons.httpclient.params.HttpConnectionParams;
56  import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
57  import org.springframework.util.AntPathMatcher;
58  
59  public class SslContextSecureProtocolSocketFactory implements SecureProtocolSocketFactory
60  {
61  
62      private SSLContext sslContext;
63      private boolean verifyHostname;
64  
65      public SslContextSecureProtocolSocketFactory( SSLContext sslContext, boolean verifyHostname )
66      {
67          this.verifyHostname = true;
68          this.sslContext = sslContext;
69          this.verifyHostname = verifyHostname;
70      }
71  
72      public SslContextSecureProtocolSocketFactory( SSLContext sslContext )
73      {
74          this( sslContext, true );
75      }
76  
77      public SslContextSecureProtocolSocketFactory( boolean verifyHostname )
78      {
79          this( (SSLContext) null, verifyHostname );
80      }
81  
82      public SslContextSecureProtocolSocketFactory( )
83      {
84          this( (SSLContext) null, true );
85      }
86  
87      public synchronized void setHostnameVerification( boolean verifyHostname )
88      {
89          this.verifyHostname = verifyHostname;
90      }
91  
92      public synchronized boolean getHostnameVerification( )
93      {
94          return this.verifyHostname;
95      }
96  
97      public Socket createSocket( String host, int port, InetAddress clientHost, int clientPort ) throws IOException, UnknownHostException
98      {
99          SSLSocketFactory sf = this.getSslSocketFactory( );
100         SSLSocket sslSocket = (SSLSocket) sf.createSocket( host, port, clientHost, clientPort );
101         this.verifyHostname( sslSocket );
102         return sslSocket;
103     }
104 
105     public Socket createSocket( String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params )
106             throws IOException, UnknownHostException, ConnectTimeoutException
107     {
108         if ( params == null )
109         {
110             throw new IllegalArgumentException( "Parameters may not be null" );
111         }
112         else
113         {
114             int timeout = params.getConnectionTimeout( );
115             Socket socket = null;
116             SSLSocketFactory socketfactory = this.getSslSocketFactory( );
117             if ( timeout == 0 )
118             {
119                 socket = socketfactory.createSocket( host, port, localAddress, localPort );
120             }
121             else
122             {
123                 socket = socketfactory.createSocket( );
124                 InetSocketAddress localaddr = new InetSocketAddress( localAddress, localPort );
125                 InetSocketAddress remoteaddr = new InetSocketAddress( host, port );
126                 socket.bind( localaddr );
127                 socket.connect( remoteaddr, timeout );
128             }
129 
130             this.verifyHostname( (SSLSocket) socket );
131             return socket;
132         }
133     }
134 
135     public Socket createSocket( String host, int port ) throws IOException, UnknownHostException
136     {
137         SSLSocketFactory sf = this.getSslSocketFactory( );
138         SSLSocket sslSocket = (SSLSocket) sf.createSocket( host, port );
139         this.verifyHostname( sslSocket );
140         return sslSocket;
141     }
142 
143     public Socket createSocket( Socket socket, String host, int port, boolean autoClose ) throws IOException, UnknownHostException
144     {
145         SSLSocketFactory sf = this.getSslSocketFactory( );
146         SSLSocket sslSocket = (SSLSocket) sf.createSocket( socket, host, port, autoClose );
147         this.verifyHostname( sslSocket );
148         return sslSocket;
149     }
150 
151     private void verifyHostname( SSLSocket socket ) throws SSLPeerUnverifiedException, UnknownHostException
152     {
153         synchronized( this )
154         {
155             if ( !this.verifyHostname )
156             {
157                 return;
158             }
159         }
160 
161         SSLSession session = socket.getSession( );
162         String hostname = session.getPeerHost( );
163 
164         try
165         {
166             InetAddress.getByName( hostname );
167         }
168         catch( UnknownHostException var10 )
169         {
170             throw new UnknownHostException( "Could not resolve SSL sessions server hostname: " + hostname );
171         }
172 
173         X509Certificate [ ] certs = (X509Certificate [ ]) ( (X509Certificate [ ]) session.getPeerCertificates( ) );
174         if ( certs != null && certs.length != 0 )
175         {
176             X500Principal subjectDN = certs [0].getSubjectX500Principal( );
177             List cns = this.getCNs( subjectDN );
178             boolean foundHostName = false;
179             Iterator i$ = cns.iterator( );
180             AntPathMatcher matcher = new AntPathMatcher( );
181             while ( i$.hasNext( ) )
182             {
183                 String cn = (String) i$.next( );
184                 if ( matcher.match( cn.toLowerCase( ), hostname.toLowerCase( ) ) )
185                 {
186                     foundHostName = true;
187                     break;
188                 }
189             }
190 
191             if ( !foundHostName )
192             {
193                 throw new SSLPeerUnverifiedException( "HTTPS hostname invalid: expected \'" + hostname + "\', received \'" + cns + "\'" );
194             }
195         }
196         else
197         {
198             throw new SSLPeerUnverifiedException( "No server certificates found!" );
199         }
200     }
201 
202     private List<String> getCNs( X500Principal subjectDN )
203     {
204         ArrayList cns = new ArrayList( );
205         StringTokenizer st = new StringTokenizer( subjectDN.getName( ), "," );
206 
207         while ( st.hasMoreTokens( ) )
208         {
209             String cnField = st.nextToken( );
210             if ( cnField.startsWith( "CN=" ) )
211             {
212                 cns.add( cnField.substring( 3 ) );
213             }
214         }
215 
216         return cns;
217     }
218 
219     protected SSLSocketFactory getSslSocketFactory( )
220     {
221         SSLSocketFactory sslSocketFactory = null;
222         synchronized( this )
223         {
224             if ( this.sslContext != null )
225             {
226                 sslSocketFactory = this.sslContext.getSocketFactory( );
227             }
228         }
229 
230         if ( sslSocketFactory == null )
231         {
232             sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault( );
233         }
234 
235         return sslSocketFactory;
236     }
237 
238     public synchronized void setSSLContext( SSLContext sslContext )
239     {
240         this.sslContext = sslContext;
241     }
242 }