1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.ssl;
17
18 import org.apache.tomcat.jni.Pool;
19 import org.apache.tomcat.jni.SSL;
20 import org.apache.tomcat.jni.SSLContext;
21 import org.jboss.netty.logging.InternalLogger;
22 import org.jboss.netty.logging.InternalLoggerFactory;
23
24 import javax.net.ssl.SSLEngine;
25 import javax.net.ssl.SSLException;
26 import java.io.File;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30
31
32
33
34 public final class OpenSslServerContext extends SslContext {
35
36 private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
37 private static final List<String> DEFAULT_CIPHERS;
38
39 static {
40 List<String> ciphers = new ArrayList<String>();
41
42 Collections.addAll(
43 ciphers,
44 "ECDHE-RSA-AES128-GCM-SHA256",
45 "ECDHE-RSA-RC4-SHA",
46 "ECDHE-RSA-AES128-SHA",
47 "ECDHE-RSA-AES256-SHA",
48 "AES128-GCM-SHA256",
49 "RC4-SHA",
50 "RC4-MD5",
51 "AES128-SHA",
52 "AES256-SHA",
53 "DES-CBC3-SHA");
54 DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
55
56 if (logger.isDebugEnabled()) {
57 logger.debug("Default cipher suite (OpenSSL): " + ciphers);
58 }
59 }
60
61 private final long aprPool;
62
63 private final List<String> ciphers = new ArrayList<String>();
64 private final List<String> unmodifiableCiphers = Collections.unmodifiableList(ciphers);
65 private final long sessionCacheSize;
66 private final long sessionTimeout;
67 private final List<String> nextProtocols;
68
69
70 private final long ctx;
71 private final OpenSslSessionStats stats;
72
73
74
75
76
77
78
79 public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException {
80 this(certChainFile, keyFile, null);
81 }
82
83
84
85
86
87
88
89
90
91 public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
92 this(null, certChainFile, keyFile, keyPassword, null, null, 0, 0);
93 }
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 public OpenSslServerContext(
114 SslBufferPool bufPool,
115 File certChainFile, File keyFile, String keyPassword,
116 Iterable<String> ciphers, Iterable<String> nextProtocols,
117 long sessionCacheSize, long sessionTimeout) throws SSLException {
118
119 super(bufPool);
120
121 OpenSsl.ensureAvailability();
122
123 if (certChainFile == null) {
124 throw new NullPointerException("certChainFile");
125 }
126 if (!certChainFile.isFile()) {
127 throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
128 }
129 if (keyFile == null) {
130 throw new NullPointerException("keyPath");
131 }
132 if (!keyFile.isFile()) {
133 throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
134 }
135 if (ciphers == null) {
136 ciphers = DEFAULT_CIPHERS;
137 }
138
139 if (keyPassword == null) {
140 keyPassword = "";
141 }
142 if (nextProtocols == null) {
143 nextProtocols = Collections.emptyList();
144 }
145
146 for (String c: ciphers) {
147 if (c == null) {
148 break;
149 }
150 this.ciphers.add(c);
151 }
152
153 List<String> nextProtoList = new ArrayList<String>();
154 for (String p: nextProtocols) {
155 if (p == null) {
156 break;
157 }
158 nextProtoList.add(p);
159 }
160 this.nextProtocols = Collections.unmodifiableList(nextProtoList);
161
162
163 aprPool = Pool.create(0);
164
165
166 boolean success = false;
167 try {
168 synchronized (OpenSslServerContext.class) {
169 try {
170 ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
171 } catch (Exception e) {
172 throw new SSLException("failed to create an SSL_CTX", e);
173 }
174
175 SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
176 SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
177 SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
178 SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE);
179 SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE);
180 SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
181
182
183 try {
184
185 StringBuilder cipherBuf = new StringBuilder();
186 for (String c: this.ciphers) {
187 cipherBuf.append(c);
188 cipherBuf.append(':');
189 }
190 cipherBuf.setLength(cipherBuf.length() - 1);
191
192 SSLContext.setCipherSuite(ctx, cipherBuf.toString());
193 } catch (SSLException e) {
194 throw e;
195 } catch (Exception e) {
196 throw new SSLException("failed to set cipher suite: " + this.ciphers, e);
197 }
198
199
200 SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10);
201
202
203 try {
204 if (!SSLContext.setCertificate(
205 ctx, certChainFile.getPath(), keyFile.getPath(), keyPassword, SSL.SSL_AIDX_RSA)) {
206 throw new SSLException("failed to set certificate: " +
207 certChainFile + " and " + keyFile + " (" + SSL.getLastError() + ')');
208 }
209 } catch (SSLException e) {
210 throw e;
211 } catch (Exception e) {
212 throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e);
213 }
214
215
216 if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
217 String error = SSL.getLastError();
218 if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
219 throw new SSLException(
220 "failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')');
221 }
222 }
223
224
225 if (!nextProtoList.isEmpty()) {
226
227 StringBuilder nextProtocolBuf = new StringBuilder();
228 for (String p: nextProtoList) {
229 nextProtocolBuf.append(p);
230 nextProtocolBuf.append(',');
231 }
232 nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
233
234 SSLContext.setNextProtos(ctx, nextProtocolBuf.toString());
235 }
236
237
238 if (sessionCacheSize > 0) {
239 this.sessionCacheSize = sessionCacheSize;
240 SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
241 } else {
242
243 this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
244
245 SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
246 }
247
248
249 if (sessionTimeout > 0) {
250 this.sessionTimeout = sessionTimeout;
251 SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
252 } else {
253
254 this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
255
256 SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
257 }
258 }
259 success = true;
260 } finally {
261 if (!success) {
262 destroyPools();
263 }
264 }
265
266 stats = new OpenSslSessionStats(ctx);
267 }
268
269 @Override
270 SslBufferPool newBufferPool() {
271 return new SslBufferPool(true, true);
272 }
273
274 @Override
275 public boolean isClient() {
276 return false;
277 }
278
279 @Override
280 public List<String> cipherSuites() {
281 return unmodifiableCiphers;
282 }
283
284 @Override
285 public long sessionCacheSize() {
286 return sessionCacheSize;
287 }
288
289 @Override
290 public long sessionTimeout() {
291 return sessionTimeout;
292 }
293
294 @Override
295 public List<String> nextProtocols() {
296 return nextProtocols;
297 }
298
299
300
301
302 public long context() {
303 return ctx;
304 }
305
306
307
308
309 public OpenSslSessionStats stats() {
310 return stats;
311 }
312
313
314
315
316 @Override
317 public SSLEngine newEngine() {
318 if (nextProtocols.isEmpty()) {
319 return new OpenSslEngine(ctx, bufferPool(), null);
320 } else {
321 return new OpenSslEngine(
322 ctx, bufferPool(), nextProtocols.get(nextProtocols.size() - 1));
323 }
324 }
325
326 @Override
327 public SSLEngine newEngine(String peerHost, int peerPort) {
328 throw new UnsupportedOperationException();
329 }
330
331
332
333
334 public void setTicketKeys(byte[] keys) {
335 if (keys != null) {
336 throw new NullPointerException("keys");
337 }
338 SSLContext.setSessionTicketKeys(ctx, keys);
339 }
340
341 @Override
342 @SuppressWarnings("FinalizeDeclaration")
343 protected void finalize() throws Throwable {
344 super.finalize();
345 synchronized (OpenSslServerContext.class) {
346 if (ctx != 0) {
347 SSLContext.free(ctx);
348 }
349 }
350
351 destroyPools();
352 }
353
354 private void destroyPools() {
355 if (aprPool != 0) {
356 Pool.destroy(aprPool);
357 }
358 }
359 }