View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.security.cert.X509Certificate;
22  
23  import javax.net.ssl.SSLContext;
24  import javax.net.ssl.SSLEngine;
25  import javax.net.ssl.SSLSession;
26  import javax.servlet.ServletRequest;
27  
28  import org.eclipse.jetty.http.BadMessageException;
29  import org.eclipse.jetty.http.HttpScheme;
30  import org.eclipse.jetty.io.ssl.SslConnection;
31  import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
32  import org.eclipse.jetty.util.IO;
33  import org.eclipse.jetty.util.TypeUtil;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  import org.eclipse.jetty.util.ssl.SslContextFactory;
37  
38  
39  /* ------------------------------------------------------------ */
40  /** Customizer that extracts the attribute from an {@link SSLContext}
41   * and sets them on the request with {@link ServletRequest#setAttribute(String, Object)}
42   * according to Servlet Specification Requirements.
43   */
44  public class SecureRequestCustomizer implements HttpConfiguration.Customizer
45  {
46      private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
47      
48      /**
49       * The name of the SSLSession attribute that will contain any cached information.
50       */
51      public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
52  
53      private boolean _sniHostCheck;
54      
55      
56      public SecureRequestCustomizer()
57      {
58          this(true);
59      }
60      
61      public SecureRequestCustomizer(boolean sniHostCheck)
62      {
63          _sniHostCheck=sniHostCheck;
64      }
65      
66      @Override
67      public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
68      {
69          if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
70          {
71              request.setScheme(HttpScheme.HTTPS.asString());
72              request.setSecure(true);
73              SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
74              SslConnection sslConnection = ssl_endp.getSslConnection();
75              SSLEngine sslEngine=sslConnection.getSSLEngine();
76              customize(sslEngine,request);
77          }
78      }
79  
80      /* ------------------------------------------------------------ */
81      /*
82       * Customise the request attributes to be set for SSL requests. <br>
83       * The requirements of the Servlet specs are:
84       * <ul>
85       * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
86       * String (since Servlet Spec 3.0).</li>
87       * <li> an attribute named "javax.servlet.request.cipher_suite" of type
88       * String.</li>
89       * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
90       * <li> an attribute named "javax.servlet.request.X509Certificate" of type
91       * java.security.cert.X509Certificate[]. This is an array of objects of type
92       * X509Certificate, the order of this array is defined as being in ascending
93       * order of trust. The first certificate in the chain is the one set by the
94       * client, the next is the one used to authenticate the first, and so on.
95       * </li>
96       * </ul>
97       *
98       * @param request
99       *                HttpRequest to be customised.
100      */
101     public void customize(SSLEngine sslEngine, Request request)
102     {
103         request.setScheme(HttpScheme.HTTPS.asString());
104         SSLSession sslSession = sslEngine.getSession();
105 
106         if (_sniHostCheck)
107         {
108             String sniName = (String)sslSession.getValue("org.eclipse.jetty.util.ssl.sniname");
109             if (sniName!=null && !sniName.equalsIgnoreCase(request.getServerName()))
110             {
111                 String wild=(String)sslSession.getValue("org.eclipse.jetty.util.ssl.sniwild");
112                 String name=request.getServerName();
113                 if (wild==null || !IO.isInDomain(name,wild)) 
114                 {
115                     LOG.warn("Host does not match SNI Name: {}/{}!={}",sniName,wild,request.getServerName());
116                     throw new BadMessageException(400,"Host does not match SNI");
117                 }
118             }
119         }
120         
121         try
122         {
123             String cipherSuite=sslSession.getCipherSuite();
124             Integer keySize;
125             X509Certificate[] certs;
126             String idStr;
127 
128             CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
129             if (cachedInfo!=null)
130             {
131                 keySize=cachedInfo.getKeySize();
132                 certs=cachedInfo.getCerts();
133                 idStr=cachedInfo.getIdStr();
134             }
135             else 
136             {
137                 keySize=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
138                 certs=SslContextFactory.getCertChain(sslSession);
139                 byte[] bytes = sslSession.getId();
140                 idStr = TypeUtil.toHexString(bytes);
141                 cachedInfo=new CachedInfo(keySize,certs,idStr);
142                 sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
143             }
144 
145             if (certs!=null)
146                 request.setAttribute("javax.servlet.request.X509Certificate",certs);
147 
148             request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
149             request.setAttribute("javax.servlet.request.key_size",keySize);
150             request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
151         }
152         catch (Exception e)
153         {
154             LOG.warn(Log.EXCEPTION,e);
155         }
156     }
157 
158     @Override
159     public String toString()
160     {
161         return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
162     }
163     
164     /* ------------------------------------------------------------ */
165     /* ------------------------------------------------------------ */
166     /* ------------------------------------------------------------ */
167     /**
168      * Simple bundle of information that is cached in the SSLSession. Stores the
169      * effective keySize and the client certificate chain.
170      */
171     private static class CachedInfo
172     {
173         private final X509Certificate[] _certs;
174         private final Integer _keySize;
175         private final String _idStr;
176 
177         CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
178         {
179             this._keySize=keySize;
180             this._certs=certs;
181             this._idStr=idStr;
182         }
183 
184         X509Certificate[] getCerts()
185         {
186             return _certs;
187         }
188 
189         Integer getKeySize()
190         {
191             return _keySize;
192         }
193 
194         String getIdStr()
195         {
196             return _idStr;
197         }
198     }
199 
200 
201 
202 }