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 }