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