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 }