1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server;
20
21 import java.security.cert.X509Certificate;
22 import java.util.concurrent.TimeUnit;
23
24 import javax.net.ssl.SSLContext;
25 import javax.net.ssl.SSLEngine;
26 import javax.net.ssl.SSLSession;
27 import javax.servlet.ServletRequest;
28
29 import org.eclipse.jetty.http.BadMessageException;
30 import org.eclipse.jetty.http.HttpField;
31 import org.eclipse.jetty.http.HttpHeader;
32 import org.eclipse.jetty.http.HttpScheme;
33 import org.eclipse.jetty.http.PreEncodedHttpField;
34 import org.eclipse.jetty.io.ssl.SslConnection;
35 import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
36 import org.eclipse.jetty.util.TypeUtil;
37 import org.eclipse.jetty.util.annotation.Name;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40 import org.eclipse.jetty.util.ssl.SniX509ExtendedKeyManager;
41 import org.eclipse.jetty.util.ssl.SslContextFactory;
42 import org.eclipse.jetty.util.ssl.X509;
43
44
45
46
47
48
49 public class SecureRequestCustomizer implements HttpConfiguration.Customizer
50 {
51 private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
52
53
54
55
56 public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
57
58 private String sslSessionAttribute = "org.eclipse.jetty.servlet.request.ssl_session";
59
60 private boolean _sniHostCheck;
61 private long _stsMaxAge=-1;
62 private boolean _stsIncludeSubDomains;
63 private HttpField _stsField;
64
65 public SecureRequestCustomizer()
66 {
67 this(true);
68 }
69
70 public SecureRequestCustomizer(@Name("sniHostCheck")boolean sniHostCheck)
71 {
72 this(sniHostCheck,-1,false);
73 }
74
75
76
77
78
79
80 public SecureRequestCustomizer(
81 @Name("sniHostCheck")boolean sniHostCheck,
82 @Name("stsMaxAgeSeconds")long stsMaxAgeSeconds,
83 @Name("stsIncludeSubdomains")boolean stsIncludeSubdomains)
84 {
85 _sniHostCheck=sniHostCheck;
86 _stsMaxAge=stsMaxAgeSeconds;
87 _stsIncludeSubDomains=stsIncludeSubdomains;
88 formatSTS();
89 }
90
91
92
93
94 public boolean isSniHostCheck()
95 {
96 return _sniHostCheck;
97 }
98
99
100
101
102 public void setSniHostCheck(boolean sniHostCheck)
103 {
104 _sniHostCheck = sniHostCheck;
105 }
106
107
108
109
110 public long getStsMaxAge()
111 {
112 return _stsMaxAge;
113 }
114
115
116
117
118
119 public void setStsMaxAge(long stsMaxAgeSeconds)
120 {
121 _stsMaxAge = stsMaxAgeSeconds;
122 formatSTS();
123 }
124
125
126
127
128
129
130 public void setStsMaxAge(long period,TimeUnit units)
131 {
132 _stsMaxAge = units.toSeconds(period);
133 formatSTS();
134 }
135
136
137
138
139 public boolean isStsIncludeSubDomains()
140 {
141 return _stsIncludeSubDomains;
142 }
143
144
145
146
147 public void setStsIncludeSubDomains(boolean stsIncludeSubDomains)
148 {
149 _stsIncludeSubDomains = stsIncludeSubDomains;
150 formatSTS();
151 }
152
153 private void formatSTS()
154 {
155 if (_stsMaxAge<0)
156 _stsField=null;
157 else
158 _stsField=new PreEncodedHttpField(HttpHeader.STRICT_TRANSPORT_SECURITY,String.format("max-age=%d%s",_stsMaxAge,_stsIncludeSubDomains?"; includeSubDomains":""));
159 }
160
161 @Override
162 public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
163 {
164 if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
165 {
166
167 if (request.getHttpURI().getScheme()==null)
168 request.setScheme(HttpScheme.HTTPS.asString());
169
170 SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
171 SslConnection sslConnection = ssl_endp.getSslConnection();
172 SSLEngine sslEngine=sslConnection.getSSLEngine();
173 customize(sslEngine,request);
174 }
175
176 if (HttpScheme.HTTPS.is(request.getScheme()))
177 customizeSecure(request);
178 }
179
180
181
182
183
184
185
186
187
188 protected void customizeSecure(Request request)
189 {
190 request.setSecure(true);
191
192 if (_stsField!=null)
193 request.getResponse().getHttpFields().add(_stsField);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 protected void customize(SSLEngine sslEngine, Request request)
219 {
220 request.setScheme(HttpScheme.HTTPS.asString());
221 SSLSession sslSession = sslEngine.getSession();
222
223 if (_sniHostCheck)
224 {
225 String name = request.getServerName();
226 X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509);
227
228 if (x509!=null && !x509.matches(name))
229 {
230 LOG.warn("Host {} does not match SNI {}",name,x509);
231 throw new BadMessageException(400,"Host does not match SNI");
232 }
233
234 if (LOG.isDebugEnabled())
235 LOG.debug("Host {} matched SNI {}",name,x509);
236 }
237
238 try
239 {
240 String cipherSuite=sslSession.getCipherSuite();
241 Integer keySize;
242 X509Certificate[] certs;
243 String idStr;
244
245 CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
246 if (cachedInfo!=null)
247 {
248 keySize=cachedInfo.getKeySize();
249 certs=cachedInfo.getCerts();
250 idStr=cachedInfo.getIdStr();
251 }
252 else
253 {
254 keySize=SslContextFactory.deduceKeyLength(cipherSuite);
255 certs=SslContextFactory.getCertChain(sslSession);
256 byte[] bytes = sslSession.getId();
257 idStr = TypeUtil.toHexString(bytes);
258 cachedInfo=new CachedInfo(keySize,certs,idStr);
259 sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
260 }
261
262 if (certs!=null)
263 request.setAttribute("javax.servlet.request.X509Certificate",certs);
264
265 request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
266 request.setAttribute("javax.servlet.request.key_size",keySize);
267 request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
268 String sessionAttribute = getSslSessionAttribute();
269 if (sessionAttribute != null && !sessionAttribute.isEmpty())
270 request.setAttribute(sessionAttribute, sslSession);
271 }
272 catch (Exception e)
273 {
274 LOG.warn(Log.EXCEPTION,e);
275 }
276 }
277
278 public void setSslSessionAttribute(String attribute)
279 {
280 this.sslSessionAttribute = attribute;
281 }
282
283 public String getSslSessionAttribute()
284 {
285 return sslSessionAttribute;
286 }
287
288 @Override
289 public String toString()
290 {
291 return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
292 }
293
294
295
296
297
298 private static class CachedInfo
299 {
300 private final X509Certificate[] _certs;
301 private final Integer _keySize;
302 private final String _idStr;
303
304 CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
305 {
306 this._keySize=keySize;
307 this._certs=certs;
308 this._idStr=idStr;
309 }
310
311 X509Certificate[] getCerts()
312 {
313 return _certs;
314 }
315
316 Integer getKeySize()
317 {
318 return _keySize;
319 }
320
321 String getIdStr()
322 {
323 return _idStr;
324 }
325 }
326 }