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