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