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