1 /*
2 * Copyright 2015 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17 package io.netty.handler.ssl;
18
19 import io.netty.util.internal.UnstableApi;
20
21 import javax.net.ssl.KeyManager;
22 import javax.net.ssl.KeyManagerFactory;
23 import javax.net.ssl.SSLEngine;
24 import javax.net.ssl.SSLException;
25 import javax.net.ssl.TrustManager;
26 import javax.net.ssl.TrustManagerFactory;
27 import java.io.File;
28 import java.io.InputStream;
29 import java.security.KeyStore;
30 import java.security.PrivateKey;
31 import java.security.Provider;
32 import java.security.cert.X509Certificate;
33 import java.util.ArrayList;
34 import java.util.List;
35
36 import static io.netty.util.internal.EmptyArrays.EMPTY_STRINGS;
37 import static io.netty.util.internal.EmptyArrays.EMPTY_X509_CERTIFICATES;
38 import static io.netty.util.internal.ObjectUtil.checkNotNull;
39
40 /**
41 * Builder for configuring a new SslContext for creation.
42 */
43 public final class SslContextBuilder {
44
45 /**
46 * Creates a builder for new client-side {@link SslContext}.
47 */
48 public static SslContextBuilder forClient() {
49 return new SslContextBuilder(false);
50 }
51
52 /**
53 * Creates a builder for new server-side {@link SslContext}.
54 *
55 * @param keyCertChainFile an X.509 certificate chain file in PEM format
56 * @param keyFile a PKCS#8 private key file in PEM format
57 * @see #keyManager(File, File)
58 */
59 public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
60 return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);
61 }
62
63 /**
64 * Creates a builder for new server-side {@link SslContext}.
65 *
66 * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
67 * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
68 * @see #keyManager(InputStream, InputStream)
69 */
70 public static SslContextBuilder forServer(InputStream keyCertChainInputStream, InputStream keyInputStream) {
71 return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream);
72 }
73
74 /**
75 * Creates a builder for new server-side {@link SslContext}.
76 *
77 * @param key a PKCS#8 private key
78 * @param keyCertChain the X.509 certificate chain
79 * @see #keyManager(PrivateKey, X509Certificate[])
80 */
81 public static SslContextBuilder forServer(PrivateKey key, X509Certificate... keyCertChain) {
82 return new SslContextBuilder(true).keyManager(key, keyCertChain);
83 }
84
85 /**
86 * Creates a builder for new server-side {@link SslContext}.
87 *
88 * @param key a PKCS#8 private key
89 * @param keyCertChain the X.509 certificate chain
90 * @see #keyManager(PrivateKey, X509Certificate[])
91 */
92 public static SslContextBuilder forServer(PrivateKey key, Iterable<? extends X509Certificate> keyCertChain) {
93 return forServer(key, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
94 }
95
96 /**
97 * Creates a builder for new server-side {@link SslContext}.
98 *
99 * @param keyCertChainFile an X.509 certificate chain file in PEM format
100 * @param keyFile a PKCS#8 private key file in PEM format
101 * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
102 * password-protected
103 * @see #keyManager(File, File, String)
104 */
105 public static SslContextBuilder forServer(
106 File keyCertChainFile, File keyFile, String keyPassword) {
107 return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword);
108 }
109
110 /**
111 * Creates a builder for new server-side {@link SslContext}.
112 *
113 * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
114 * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
115 * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
116 * password-protected
117 * @see #keyManager(InputStream, InputStream, String)
118 */
119 public static SslContextBuilder forServer(
120 InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) {
121 return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream, keyPassword);
122 }
123
124 /**
125 * Creates a builder for new server-side {@link SslContext}.
126 *
127 * @param key a PKCS#8 private key
128 * @param keyCertChain the X.509 certificate chain
129 * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
130 * password-protected
131 * @see #keyManager(File, File, String)
132 */
133 public static SslContextBuilder forServer(
134 PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
135 return new SslContextBuilder(true).keyManager(key, keyPassword, keyCertChain);
136 }
137
138 /**
139 * Creates a builder for new server-side {@link SslContext}.
140 *
141 * @param key a PKCS#8 private key
142 * @param keyCertChain the X.509 certificate chain
143 * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
144 * password-protected
145 * @see #keyManager(File, File, String)
146 */
147 public static SslContextBuilder forServer(
148 PrivateKey key, String keyPassword, Iterable<? extends X509Certificate> keyCertChain) {
149 return forServer(key, keyPassword, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
150 }
151
152 /**
153 * Creates a builder for new server-side {@link SslContext}.
154 *
155 * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
156 * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
157 *
158 * @param keyManagerFactory non-{@code null} factory for server's private key
159 * @see #keyManager(KeyManagerFactory)
160 */
161 public static SslContextBuilder forServer(KeyManagerFactory keyManagerFactory) {
162 return new SslContextBuilder(true).keyManager(keyManagerFactory);
163 }
164
165 /**
166 * Creates a builder for new server-side {@link SslContext} with {@link KeyManager}.
167 *
168 * @param keyManager non-{@code null} KeyManager for server's private key
169 */
170 public static SslContextBuilder forServer(KeyManager keyManager) {
171 return new SslContextBuilder(true).keyManager(keyManager);
172 }
173
174 private final boolean forServer;
175 private SslProvider provider;
176 private Provider sslContextProvider;
177 private X509Certificate[] trustCertCollection;
178 private TrustManagerFactory trustManagerFactory;
179 private X509Certificate[] keyCertChain;
180 private PrivateKey key;
181 private String keyPassword;
182 private KeyManagerFactory keyManagerFactory;
183 private Iterable<String> ciphers;
184 private CipherSuiteFilter cipherFilter = IdentityCipherSuiteFilter.INSTANCE;
185 private ApplicationProtocolConfig apn;
186 private long sessionCacheSize;
187 private long sessionTimeout;
188 private ClientAuth clientAuth = ClientAuth.NONE;
189 private String[] protocols;
190 private boolean startTls;
191 private boolean enableOcsp;
192 private String keyStoreType = KeyStore.getDefaultType();
193
194 private SslContextBuilder(boolean forServer) {
195 this.forServer = forServer;
196 }
197
198 /**
199 * The {@link SslContext} implementation to use. {@code null} uses the default one.
200 */
201 public SslContextBuilder sslProvider(SslProvider provider) {
202 this.provider = provider;
203 return this;
204 }
205
206 /**
207 * Sets the {@link KeyStore} type that should be used. {@code null} uses the default one.
208 */
209 public SslContextBuilder keyStoreType(String keyStoreType) {
210 this.keyStoreType = keyStoreType;
211 return this;
212 }
213
214 /**
215 * The SSLContext {@link Provider} to use. {@code null} uses the default one. This is only
216 * used with {@link SslProvider#JDK}.
217 */
218 public SslContextBuilder sslContextProvider(Provider sslContextProvider) {
219 this.sslContextProvider = sslContextProvider;
220 return this;
221 }
222
223 /**
224 * Trusted certificates for verifying the remote endpoint's certificate. The file should
225 * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
226 */
227 public SslContextBuilder trustManager(File trustCertCollectionFile) {
228 try {
229 return trustManager(SslContext.toX509Certificates(trustCertCollectionFile));
230 } catch (Exception e) {
231 throw new IllegalArgumentException("File does not contain valid certificates: "
232 + trustCertCollectionFile, e);
233 }
234 }
235
236 /**
237 * Trusted certificates for verifying the remote endpoint's certificate. The input stream should
238 * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
239 */
240 public SslContextBuilder trustManager(InputStream trustCertCollectionInputStream) {
241 try {
242 return trustManager(SslContext.toX509Certificates(trustCertCollectionInputStream));
243 } catch (Exception e) {
244 throw new IllegalArgumentException("Input stream does not contain valid certificates.", e);
245 }
246 }
247
248 /**
249 * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
250 */
251 public SslContextBuilder trustManager(X509Certificate... trustCertCollection) {
252 this.trustCertCollection = trustCertCollection != null ? trustCertCollection.clone() : null;
253 trustManagerFactory = null;
254 return this;
255 }
256
257 /**
258 * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
259 */
260 public SslContextBuilder trustManager(Iterable<? extends X509Certificate> trustCertCollection) {
261 return trustManager(toArray(trustCertCollection, EMPTY_X509_CERTIFICATES));
262 }
263
264 /**
265 * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default.
266 */
267 public SslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
268 trustCertCollection = null;
269 this.trustManagerFactory = trustManagerFactory;
270 return this;
271 }
272
273 /**
274 * A single trusted manager for verifying the remote endpoint's certificate.
275 * This is helpful when custom implementation of {@link TrustManager} is needed.
276 * Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
277 * specified {@link TrustManager} will be created, thus all the requirements specified in
278 * {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
279 */
280 public SslContextBuilder trustManager(TrustManager trustManager) {
281 this.trustManagerFactory = new TrustManagerFactoryWrapper(trustManager);
282 trustCertCollection = null;
283 return this;
284 }
285
286 /**
287 * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
288 * be {@code null} for client contexts, which disables mutual authentication.
289 *
290 * @param keyCertChainFile an X.509 certificate chain file in PEM format
291 * @param keyFile a PKCS#8 private key file in PEM format
292 */
293 public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) {
294 return keyManager(keyCertChainFile, keyFile, null);
295 }
296
297 /**
298 * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
299 * be {@code null} for client contexts, which disables mutual authentication.
300 *
301 * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
302 * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
303 */
304 public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) {
305 return keyManager(keyCertChainInputStream, keyInputStream, null);
306 }
307
308 /**
309 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
310 * be {@code null} for client contexts, which disables mutual authentication.
311 *
312 * @param key a PKCS#8 private key
313 * @param keyCertChain an X.509 certificate chain
314 */
315 public SslContextBuilder keyManager(PrivateKey key, X509Certificate... keyCertChain) {
316 return keyManager(key, null, keyCertChain);
317 }
318
319 /**
320 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
321 * be {@code null} for client contexts, which disables mutual authentication.
322 *
323 * @param key a PKCS#8 private key
324 * @param keyCertChain an X.509 certificate chain
325 */
326 public SslContextBuilder keyManager(PrivateKey key, Iterable<? extends X509Certificate> keyCertChain) {
327 return keyManager(key, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
328 }
329
330 /**
331 * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
332 * be {@code null} for client contexts, which disables mutual authentication.
333 *
334 * @param keyCertChainFile an X.509 certificate chain file in PEM format
335 * @param keyFile a PKCS#8 private key file in PEM format
336 * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
337 * password-protected
338 */
339 public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String keyPassword) {
340 X509Certificate[] keyCertChain;
341 PrivateKey key;
342 try {
343 keyCertChain = SslContext.toX509Certificates(keyCertChainFile);
344 } catch (Exception e) {
345 throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
346 }
347 try {
348 key = SslContext.toPrivateKey(keyFile, keyPassword);
349 } catch (Exception e) {
350 throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
351 }
352 return keyManager(key, keyPassword, keyCertChain);
353 }
354
355 /**
356 * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
357 * be {@code null} for client contexts, which disables mutual authentication.
358 *
359 * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
360 * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
361 * @param keyPassword the password of the {@code keyInputStream}, or {@code null} if it's not
362 * password-protected
363 */
364 public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream,
365 String keyPassword) {
366 X509Certificate[] keyCertChain;
367 PrivateKey key;
368 try {
369 keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream);
370 } catch (Exception e) {
371 throw new IllegalArgumentException("Input stream not contain valid certificates.", e);
372 }
373 try {
374 key = SslContext.toPrivateKey(keyInputStream, keyPassword);
375 } catch (Exception e) {
376 throw new IllegalArgumentException("Input stream does not contain valid private key.", e);
377 }
378 return keyManager(key, keyPassword, keyCertChain);
379 }
380
381 /**
382 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
383 * be {@code null} for client contexts, which disables mutual authentication.
384 *
385 * @param key a PKCS#8 private key file
386 * @param keyPassword the password of the {@code key}, or {@code null} if it's not
387 * password-protected
388 * @param keyCertChain an X.509 certificate chain
389 */
390 public SslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
391 if (forServer) {
392 checkNotNull(keyCertChain, "keyCertChain required for servers");
393 if (keyCertChain.length == 0) {
394 throw new IllegalArgumentException("keyCertChain must be non-empty");
395 }
396 checkNotNull(key, "key required for servers");
397 }
398 if (keyCertChain == null || keyCertChain.length == 0) {
399 this.keyCertChain = null;
400 } else {
401 for (X509Certificate cert: keyCertChain) {
402 if (cert == null) {
403 throw new IllegalArgumentException("keyCertChain contains null entry");
404 }
405 }
406 this.keyCertChain = keyCertChain.clone();
407 }
408 this.key = key;
409 this.keyPassword = keyPassword;
410 keyManagerFactory = null;
411 return this;
412 }
413
414 /**
415 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
416 * be {@code null} for client contexts, which disables mutual authentication.
417 *
418 * @param key a PKCS#8 private key file
419 * @param keyPassword the password of the {@code key}, or {@code null} if it's not
420 * password-protected
421 * @param keyCertChain an X.509 certificate chain
422 */
423 public SslContextBuilder keyManager(PrivateKey key, String keyPassword,
424 Iterable<? extends X509Certificate> keyCertChain) {
425 return keyManager(key, keyPassword, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
426 }
427
428 /**
429 * Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
430 * client contexts, which disables mutual authentication. Using a {@link KeyManagerFactory}
431 * is only supported for {@link SslProvider#JDK} or {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT}
432 * if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
433 * {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
434 * you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
435 *
436 * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
437 * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
438 */
439 public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
440 if (forServer) {
441 checkNotNull(keyManagerFactory, "keyManagerFactory required for servers");
442 }
443 keyCertChain = null;
444 key = null;
445 keyPassword = null;
446 this.keyManagerFactory = keyManagerFactory;
447 return this;
448 }
449
450 /**
451 * A single key manager managing the identity information of this host.
452 * This is helpful when custom implementation of {@link KeyManager} is needed.
453 * Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
454 * {@link KeyManager} will be created, thus all the requirements specified in
455 * {@link #keyManager(KeyManagerFactory keyManagerFactory)} also apply here.
456 */
457 public SslContextBuilder keyManager(KeyManager keyManager) {
458 if (forServer) {
459 checkNotNull(keyManager, "keyManager required for servers");
460 }
461 if (keyManager != null) {
462 this.keyManagerFactory = new KeyManagerFactoryWrapper(keyManager);
463 } else {
464 this.keyManagerFactory = null;
465 }
466 keyCertChain = null;
467 key = null;
468 keyPassword = null;
469 return this;
470 }
471
472 /**
473 * The cipher suites to enable, in the order of preference. {@code null} to use default
474 * cipher suites.
475 */
476 public SslContextBuilder ciphers(Iterable<String> ciphers) {
477 return ciphers(ciphers, IdentityCipherSuiteFilter.INSTANCE);
478 }
479
480 /**
481 * The cipher suites to enable, in the order of preference. {@code cipherFilter} will be
482 * applied to the ciphers before use. If {@code ciphers} is {@code null}, then the default
483 * cipher suites will be used.
484 */
485 public SslContextBuilder ciphers(Iterable<String> ciphers, CipherSuiteFilter cipherFilter) {
486 this.cipherFilter = checkNotNull(cipherFilter, "cipherFilter");
487 this.ciphers = ciphers;
488 return this;
489 }
490
491 /**
492 * Application protocol negotiation configuration. {@code null} disables support.
493 */
494 public SslContextBuilder applicationProtocolConfig(ApplicationProtocolConfig apn) {
495 this.apn = apn;
496 return this;
497 }
498
499 /**
500 * Set the size of the cache used for storing SSL session objects. {@code 0} to use the
501 * default value.
502 */
503 public SslContextBuilder sessionCacheSize(long sessionCacheSize) {
504 this.sessionCacheSize = sessionCacheSize;
505 return this;
506 }
507
508 /**
509 * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
510 * default value.
511 */
512 public SslContextBuilder sessionTimeout(long sessionTimeout) {
513 this.sessionTimeout = sessionTimeout;
514 return this;
515 }
516
517 /**
518 * Sets the client authentication mode.
519 */
520 public SslContextBuilder clientAuth(ClientAuth clientAuth) {
521 this.clientAuth = checkNotNull(clientAuth, "clientAuth");
522 return this;
523 }
524
525 /**
526 * The TLS protocol versions to enable.
527 * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
528 * @see SSLEngine#setEnabledCipherSuites(String[])
529 */
530 public SslContextBuilder protocols(String... protocols) {
531 this.protocols = protocols == null ? null : protocols.clone();
532 return this;
533 }
534
535 /**
536 * The TLS protocol versions to enable.
537 * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
538 * @see SSLEngine#setEnabledCipherSuites(String[])
539 */
540 public SslContextBuilder protocols(Iterable<String> protocols) {
541 return protocols(toArray(protocols, EMPTY_STRINGS));
542 }
543
544 /**
545 * {@code true} if the first write request shouldn't be encrypted.
546 */
547 public SslContextBuilder startTls(boolean startTls) {
548 this.startTls = startTls;
549 return this;
550 }
551
552 /**
553 * Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP
554 * stapling and an exception will be thrown upon {@link #build()}.
555 *
556 * @see OpenSsl#isOcspSupported()
557 */
558 @UnstableApi
559 public SslContextBuilder enableOcsp(boolean enableOcsp) {
560 this.enableOcsp = enableOcsp;
561 return this;
562 }
563
564 /**
565 * Create new {@code SslContext} instance with configured settings.
566 * <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
567 * responsible for releasing this object, or else native memory may leak.
568 */
569 public SslContext build() throws SSLException {
570 if (forServer) {
571 return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
572 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
573 ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
574 enableOcsp, keyStoreType);
575 } else {
576 return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
577 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
578 ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp, keyStoreType);
579 }
580 }
581
582 private static <T> T[] toArray(Iterable<? extends T> iterable, T[] prototype) {
583 if (iterable == null) {
584 return null;
585 }
586 final List<T> list = new ArrayList<T>();
587 for (T element : iterable) {
588 list.add(element);
589 }
590 return list.toArray(prototype);
591 }
592 }
593