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
219 protected void customize(SSLEngine sslEngine, Request request)
220 {
221 request.setScheme(HttpScheme.HTTPS.asString());
222 SSLSession sslSession = sslEngine.getSession();
223
224 if (_sniHostCheck)
225 {
226 String name = request.getServerName();
227 X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509);
228
229 if (x509!=null && !x509.matches(name))
230 {
231 LOG.warn("Host {} does not match SNI {}",name,x509);
232 throw new BadMessageException(400,"Host does not match SNI");
233 }
234
235 if (LOG.isDebugEnabled())
236 LOG.debug("Host {} matched SNI {}",name,x509);
237 }
238
239 try
240 {
241 String cipherSuite=sslSession.getCipherSuite();
242 Integer keySize;
243 X509Certificate[] certs;
244 String idStr;
245
246 CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
247 if (cachedInfo!=null)
248 {
249 keySize=cachedInfo.getKeySize();
250 certs=cachedInfo.getCerts();
251 idStr=cachedInfo.getIdStr();
252 }
253 else
254 {
255 keySize=SslContextFactory.deduceKeyLength(cipherSuite);
256 certs=SslContextFactory.getCertChain(sslSession);
257 byte[] bytes = sslSession.getId();
258 idStr = TypeUtil.toHexString(bytes);
259 cachedInfo=new CachedInfo(keySize,certs,idStr);
260 sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
261 }
262
263 if (certs!=null)
264 request.setAttribute("javax.servlet.request.X509Certificate",certs);
265
266 request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
267 request.setAttribute("javax.servlet.request.key_size",keySize);
268 request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
269 request.setAttribute(getSslSessionAttribute(), sslSession);
270 }
271 catch (Exception e)
272 {
273 LOG.warn(Log.EXCEPTION,e);
274 }
275 }
276
277 public void setSslSessionAttribute(String attribute)
278 {
279 this.sslSessionAttribute = attribute;
280 }
281
282 public String getSslSessionAttribute()
283 {
284 return sslSessionAttribute;
285 }
286
287 @Override
288 public String toString()
289 {
290 return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
291 }
292
293
294
295
296
297 private static class CachedInfo
298 {
299 private final X509Certificate[] _certs;
300 private final Integer _keySize;
301 private final String _idStr;
302
303 CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
304 {
305 this._keySize=keySize;
306 this._certs=certs;
307 this._idStr=idStr;
308 }
309
310 X509Certificate[] getCerts()
311 {
312 return _certs;
313 }
314
315 Integer getKeySize()
316 {
317 return _keySize;
318 }
319
320 String getIdStr()
321 {
322 return _idStr;
323 }
324 }
325 }