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.TypeUtil;
33  import org.eclipse.jetty.util.log.Log;
34  import org.eclipse.jetty.util.log.Logger;
35  import org.eclipse.jetty.util.ssl.SniX509ExtendedKeyManager;
36  import org.eclipse.jetty.util.ssl.SslContextFactory;
37  import org.eclipse.jetty.util.ssl.X509;
38  
39  /**
40   * <p>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.</p>
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       * <p>
82       * Customizes the request attributes to be set for SSL requests.
83       * </p>
84       * <p>
85       * The requirements of the Servlet specs are:
86       * </p>
87       * <ul>
88       * <li>an attribute named "javax.servlet.request.ssl_session_id" of type String (since Servlet Spec 3.0).</li>
89       * <li>an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
90       * <li>an attribute named "javax.servlet.request.key_size" of type Integer.</li>
91       * <li>an attribute named "javax.servlet.request.X509Certificate" of type java.security.cert.X509Certificate[]. This
92       * is an array of objects of type X509Certificate, the order of this array is defined as being in ascending order of
93       * trust. The first certificate in the chain is the one set by the client, the next is the one used to authenticate
94       * the first, and so on.</li>
95       * </ul>
96       * 
97       * @param sslEngine
98       *            the sslEngine to be customized.
99       * @param request
100      *            HttpRequest to be customized.
101      */
102     public void customize(SSLEngine sslEngine, Request request)
103     {
104         request.setScheme(HttpScheme.HTTPS.asString());
105         SSLSession sslSession = sslEngine.getSession();
106 
107         if (_sniHostCheck)
108         {
109             String name = request.getServerName();
110             X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509);
111 
112             if (x509!=null && !x509.matches(name))
113             {
114                 LOG.warn("Host {} does not match SNI {}",name,x509);
115                 throw new BadMessageException(400,"Host does not match SNI");
116             }
117 
118             if (LOG.isDebugEnabled())
119                 LOG.debug("Host {} matched SNI {}",name,x509);
120         }
121 
122         try
123         {
124             String cipherSuite=sslSession.getCipherSuite();
125             Integer keySize;
126             X509Certificate[] certs;
127             String idStr;
128 
129             CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
130             if (cachedInfo!=null)
131             {
132                 keySize=cachedInfo.getKeySize();
133                 certs=cachedInfo.getCerts();
134                 idStr=cachedInfo.getIdStr();
135             }
136             else
137             {
138                 keySize=SslContextFactory.deduceKeyLength(cipherSuite);
139                 certs=SslContextFactory.getCertChain(sslSession);
140                 byte[] bytes = sslSession.getId();
141                 idStr = TypeUtil.toHexString(bytes);
142                 cachedInfo=new CachedInfo(keySize,certs,idStr);
143                 sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
144             }
145 
146             if (certs!=null)
147                 request.setAttribute("javax.servlet.request.X509Certificate",certs);
148 
149             request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
150             request.setAttribute("javax.servlet.request.key_size",keySize);
151             request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
152         }
153         catch (Exception e)
154         {
155             LOG.warn(Log.EXCEPTION,e);
156         }
157     }
158 
159     @Override
160     public String toString()
161     {
162         return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
163     }
164 
165     /**
166      * Simple bundle of information that is cached in the SSLSession. Stores the
167      * effective keySize and the client certificate chain.
168      */
169     private static class CachedInfo
170     {
171         private final X509Certificate[] _certs;
172         private final Integer _keySize;
173         private final String _idStr;
174 
175         CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
176         {
177             this._keySize=keySize;
178             this._certs=certs;
179             this._idStr=idStr;
180         }
181 
182         X509Certificate[] getCerts()
183         {
184             return _certs;
185         }
186 
187         Integer getKeySize()
188         {
189             return _keySize;
190         }
191 
192         String getIdStr()
193         {
194             return _idStr;
195         }
196     }
197 }