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