View Javadoc
1   /*
2    * Copyright 2019-2021 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * 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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package nl.altindag.ssl;
18  
19  import nl.altindag.ssl.exception.GenericKeyStoreException;
20  import nl.altindag.ssl.exception.GenericSecurityException;
21  import nl.altindag.ssl.model.IdentityMaterial;
22  import nl.altindag.ssl.model.KeyStoreHolder;
23  import nl.altindag.ssl.model.SSLMaterial;
24  import nl.altindag.ssl.model.TrustMaterial;
25  import nl.altindag.ssl.util.KeyManagerUtils;
26  import nl.altindag.ssl.util.KeyStoreUtils;
27  import nl.altindag.ssl.util.SSLContextUtils;
28  import nl.altindag.ssl.util.SSLParametersUtils;
29  import nl.altindag.ssl.util.SSLSessionUtils;
30  import nl.altindag.ssl.util.SSLSocketUtils;
31  import nl.altindag.ssl.util.StringUtils;
32  import nl.altindag.ssl.util.TrustManagerUtils;
33  import nl.altindag.ssl.util.UriUtils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import javax.net.ssl.HostnameVerifier;
38  import javax.net.ssl.KeyManagerFactory;
39  import javax.net.ssl.SSLContext;
40  import javax.net.ssl.SSLEngine;
41  import javax.net.ssl.SSLParameters;
42  import javax.net.ssl.SSLServerSocketFactory;
43  import javax.net.ssl.SSLSocketFactory;
44  import javax.net.ssl.TrustManagerFactory;
45  import javax.net.ssl.X509ExtendedKeyManager;
46  import javax.net.ssl.X509ExtendedTrustManager;
47  import javax.net.ssl.X509KeyManager;
48  import javax.net.ssl.X509TrustManager;
49  import java.io.InputStream;
50  import java.net.URI;
51  import java.nio.file.Path;
52  import java.security.Key;
53  import java.security.KeyStore;
54  import java.security.Provider;
55  import java.security.SecureRandom;
56  import java.security.cert.Certificate;
57  import java.security.cert.X509Certificate;
58  import java.util.AbstractMap;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Collections;
62  import java.util.HashMap;
63  import java.util.List;
64  import java.util.Map;
65  import java.util.Optional;
66  import java.util.stream.Collectors;
67  
68  import static java.util.Objects.isNull;
69  import static java.util.Objects.nonNull;
70  
71  /**
72   * @author Hakan Altindag
73   */
74  public final class SSLFactory {
75  
76      private static final Logger LOGGER = LoggerFactory.getLogger(SSLFactory.class);
77  
78      private final SSLMaterial sslMaterial;
79  
80      private SSLFactory(SSLMaterial sslMaterial) {
81          this.sslMaterial = sslMaterial;
82      }
83  
84      public List<KeyStoreHolder> getIdentities() {
85          return sslMaterial.getIdentityMaterial().getIdentities();
86      }
87  
88      public List<KeyStoreHolder> getTrustStores() {
89          return sslMaterial.getTrustMaterial().getTrustStores();
90      }
91  
92      public SSLContext getSslContext() {
93          return sslMaterial.getSslContext();
94      }
95  
96      public SSLSocketFactory getSslSocketFactory() {
97          return SSLSocketUtils.createSslSocketFactory(sslMaterial.getSslContext(), getSslParameters());
98      }
99  
100     public SSLServerSocketFactory getSslServerSocketFactory() {
101         return SSLSocketUtils.createSslServerSocketFactory(sslMaterial.getSslContext(), getSslParameters());
102     }
103 
104     public Optional<X509ExtendedKeyManager> getKeyManager() {
105         return Optional.ofNullable(sslMaterial.getIdentityMaterial().getKeyManager());
106     }
107 
108     public Optional<KeyManagerFactory> getKeyManagerFactory() {
109         return getKeyManager().map(KeyManagerUtils::createKeyManagerFactory);
110     }
111 
112     public Optional<X509ExtendedTrustManager> getTrustManager() {
113         return Optional.ofNullable(sslMaterial.getTrustMaterial().getTrustManager());
114     }
115 
116     public Optional<TrustManagerFactory> getTrustManagerFactory() {
117         return getTrustManager().map(TrustManagerUtils::createTrustManagerFactory);
118     }
119 
120     public List<X509Certificate> getTrustedCertificates() {
121         return getTrustManager()
122                 .map(X509ExtendedTrustManager::getAcceptedIssuers)
123                 .flatMap(x509Certificates -> Optional.of(Arrays.asList(x509Certificates)))
124                 .map(Collections::unmodifiableList)
125                 .orElse(Collections.emptyList());
126     }
127 
128     public HostnameVerifier getHostnameVerifier() {
129         return sslMaterial.getHostnameVerifier();
130     }
131 
132     public List<String> getCiphers() {
133         return sslMaterial.getCiphers();
134     }
135 
136     public List<String> getProtocols() {
137         return sslMaterial.getProtocols();
138     }
139 
140     public SSLParameters getSslParameters() {
141         return SSLParametersUtils.copy(sslMaterial.getSslParameters());
142     }
143 
144     public SSLEngine getSSLEngine() {
145         return getSSLEngine(null, null);
146     }
147 
148     public SSLEngine getSSLEngine(String peerHost, Integer peerPort) {
149         SSLEngine sslEngine;
150         if (nonNull(peerHost) && nonNull(peerPort)) {
151             sslEngine = sslMaterial.getSslContext().createSSLEngine(peerHost, peerPort);
152         } else {
153             sslEngine = sslMaterial.getSslContext().createSSLEngine();
154         }
155 
156         sslEngine.setSSLParameters(getSslParameters());
157         return sslEngine;
158     }
159 
160     public static Builder builder() {
161         return new Builder();
162     }
163 
164     public static class Builder {
165 
166         private static final String TRUST_STORE_VALIDATION_EXCEPTION_MESSAGE = "TrustStore details are empty, which are required to be present when SSL/TLS is enabled";
167         private static final String IDENTITY_VALIDATION_EXCEPTION_MESSAGE = "Identity details are empty, which are required to be present when SSL/TLS is enabled";
168         private static final String IDENTITY_AND_TRUST_MATERIAL_VALIDATION_EXCEPTION_MESSAGE = "Could not create instance of SSLFactory because Identity " +
169                 "and Trust material are not present. Please provide at least a Trust material.";
170 
171         private static final char[] EMPTY_PASSWORD = {};
172 
173         private String sslContextAlgorithm = "TLS";
174         private Provider securityProvider = null;
175         private String securityProviderName = null;
176         private SecureRandom secureRandom = null;
177         private HostnameVerifier hostnameVerifier = (host, sslSession) -> host.equalsIgnoreCase(sslSession.getPeerHost());
178 
179         private final List<KeyStoreHolder> identities = new ArrayList<>();
180         private final List<KeyStoreHolder> trustStores = new ArrayList<>();
181         private final List<X509ExtendedKeyManager> identityManagers = new ArrayList<>();
182         private final List<X509ExtendedTrustManager> trustManagers = new ArrayList<>();
183         private final SSLParameters sslParameters = new SSLParameters();
184         private final Map<String, List<URI>> preferredClientAliasToHost = new HashMap<>();
185 
186         private boolean passwordCachingEnabled = false;
187         private boolean swappableKeyManagerEnabled = false;
188         private boolean swappableTrustManagerEnabled = false;
189 
190         private int sessionTimeoutInSeconds = -1;
191         private int sessionCacheSizeInBytes = -1;
192 
193         private Builder() {}
194 
195         public Builder withSystemTrustMaterial() {
196             TrustManagerUtils.createTrustManagerWithSystemTrustedCertificates().ifPresent(trustManagers::add);
197             return this;
198         }
199 
200         public Builder withDefaultTrustMaterial() {
201             trustManagers.add(TrustManagerUtils.createTrustManagerWithJdkTrustedCertificates());
202             return this;
203         }
204 
205         /**
206          * Enables the possibility to swap the underlying TrustManager at runtime.
207          * After this option has been enabled the TrustManager can be swapped
208          * with {@link TrustManagerUtils#swapTrustManager(X509TrustManager, X509TrustManager) TrustManagerUtils#swapTrustManager(swappableTrustManager, newTrustManager)}
209          *
210          * @return {@link Builder}
211          */
212         public Builder withSwappableTrustMaterial() {
213             swappableTrustManagerEnabled = true;
214             return this;
215         }
216 
217         public <T extends X509TrustManager> Builder withTrustMaterial(T trustManager) {
218             trustManagers.add(TrustManagerUtils.wrapIfNeeded(trustManager));
219             return this;
220         }
221 
222         public <T extends TrustManagerFactory> Builder withTrustMaterial(T trustManagerFactory) {
223             X509ExtendedTrustManager trustManager = TrustManagerUtils.getTrustManager(trustManagerFactory);
224             this.trustManagers.add(trustManager);
225             return this;
226         }
227 
228         public Builder withTrustMaterial(String trustStorePath, char[] trustStorePassword) {
229             return withTrustMaterial(trustStorePath, trustStorePassword, KeyStore.getDefaultType());
230         }
231 
232         public Builder withTrustMaterial(String trustStorePath, char[] trustStorePassword, String trustStoreType) {
233             if (StringUtils.isBlank(trustStorePath)) {
234                 throw new GenericKeyStoreException(TRUST_STORE_VALIDATION_EXCEPTION_MESSAGE);
235             }
236 
237             KeyStore trustStore = KeyStoreUtils.loadKeyStore(trustStorePath, trustStorePassword, trustStoreType);
238             KeyStoreHolder trustStoreHolder = new KeyStoreHolder(trustStore, trustStorePassword);
239             trustStores.add(trustStoreHolder);
240 
241             return this;
242         }
243 
244         public Builder withTrustMaterial(Path trustStorePath, char[] trustStorePassword) {
245             return withTrustMaterial(trustStorePath, trustStorePassword, KeyStore.getDefaultType());
246         }
247 
248         public Builder withTrustMaterial(Path trustStorePath, char[] trustStorePassword, String trustStoreType) {
249             if (isNull(trustStorePath) || StringUtils.isBlank(trustStoreType)) {
250                 throw new GenericKeyStoreException(TRUST_STORE_VALIDATION_EXCEPTION_MESSAGE);
251             }
252 
253             KeyStore trustStore = KeyStoreUtils.loadKeyStore(trustStorePath, trustStorePassword, trustStoreType);
254             KeyStoreHolder trustStoreHolder = new KeyStoreHolder(trustStore, trustStorePassword);
255             trustStores.add(trustStoreHolder);
256 
257             return this;
258         }
259 
260         public Builder withTrustMaterial(InputStream trustStoreStream, char[] trustStorePassword) {
261             return withTrustMaterial(trustStoreStream, trustStorePassword, KeyStore.getDefaultType());
262         }
263 
264         public Builder withTrustMaterial(InputStream trustStoreStream, char[] trustStorePassword, String trustStoreType) {
265             KeyStore trustStore = KeyStoreUtils.loadKeyStore(trustStoreStream, trustStorePassword, trustStoreType);
266             KeyStoreHolder trustStoreHolder = new KeyStoreHolder(trustStore, trustStorePassword);
267             trustStores.add(trustStoreHolder);
268             return this;
269         }
270 
271         public Builder withTrustMaterial(KeyStore trustStore) {
272             withTrustMaterial(trustStore, EMPTY_PASSWORD);
273             return this;
274         }
275 
276         public Builder withTrustMaterial(KeyStore trustStore, char[] trustStorePassword) {
277             validateKeyStore(trustStore, TRUST_STORE_VALIDATION_EXCEPTION_MESSAGE);
278             KeyStoreHolder trustStoreHolder = new KeyStoreHolder(trustStore, trustStorePassword);
279             trustStores.add(trustStoreHolder);
280 
281             return this;
282         }
283 
284         @SafeVarargs
285         public final <T extends Certificate> Builder withTrustMaterial(T... certificates) {
286             return withTrustMaterial(Arrays.asList(certificates));
287         }
288 
289         public <T extends Certificate> Builder withTrustMaterial(List<T> certificates) {
290             KeyStore trustStore = KeyStoreUtils.createTrustStore(certificates);
291             KeyStoreHolder trustStoreHolder = new KeyStoreHolder(trustStore, KeyStoreUtils.DUMMY_PASSWORD.toCharArray());
292             trustStores.add(trustStoreHolder);
293             return this;
294         }
295 
296         public Builder withIdentityMaterial(String identityStorePath, char[] identityStorePassword) {
297             return withIdentityMaterial(identityStorePath, identityStorePassword, identityStorePassword, KeyStore.getDefaultType());
298         }
299 
300         public Builder withIdentityMaterial(String identityStorePath, char[] identityStorePassword, char[] identityPassword) {
301             return withIdentityMaterial(identityStorePath, identityStorePassword, identityPassword, KeyStore.getDefaultType());
302         }
303 
304         public Builder withIdentityMaterial(String identityStorePath, char[] identityStorePassword, String identityStoreType) {
305             return withIdentityMaterial(identityStorePath, identityStorePassword, identityStorePassword, identityStoreType);
306         }
307 
308         public Builder withIdentityMaterial(String identityStorePath, char[] identityStorePassword, char[] identityPassword, String identityStoreType) {
309             if (StringUtils.isBlank(identityStorePath) || StringUtils.isBlank(identityStoreType)) {
310                 throw new GenericKeyStoreException(IDENTITY_VALIDATION_EXCEPTION_MESSAGE);
311             }
312 
313             KeyStore identity = KeyStoreUtils.loadKeyStore(identityStorePath, identityStorePassword, identityStoreType);
314             KeyStoreHolder identityHolder = new KeyStoreHolder(identity, identityStorePassword, identityPassword);
315             identities.add(identityHolder);
316             return this;
317         }
318 
319         public Builder withIdentityMaterial(Path identityStorePath, char[] identityStorePassword) {
320             return withIdentityMaterial(identityStorePath, identityStorePassword, identityStorePassword, KeyStore.getDefaultType());
321         }
322 
323         public Builder withIdentityMaterial(Path identityStorePath, char[] identityStorePassword, char[] identityPassword) {
324             return withIdentityMaterial(identityStorePath, identityStorePassword, identityPassword, KeyStore.getDefaultType());
325         }
326 
327         public Builder withIdentityMaterial(Path identityStorePath, char[] identityStorePassword, String identityStoreType) {
328             return withIdentityMaterial(identityStorePath, identityStorePassword, identityStorePassword, identityStoreType);
329         }
330 
331         public Builder withIdentityMaterial(Path identityStorePath, char[] identityStorePassword, char[] identityPassword, String identityStoreType) {
332             if (isNull(identityStorePath) || StringUtils.isBlank(identityStoreType)) {
333                 throw new GenericKeyStoreException(IDENTITY_VALIDATION_EXCEPTION_MESSAGE);
334             }
335 
336             KeyStore identity = KeyStoreUtils.loadKeyStore(identityStorePath, identityStorePassword, identityStoreType);
337             KeyStoreHolder identityHolder = new KeyStoreHolder(identity, identityStorePassword, identityPassword);
338             identities.add(identityHolder);
339             return this;
340         }
341 
342         public Builder withIdentityMaterial(InputStream identityStream, char[] identityStorePassword) {
343             return withIdentityMaterial(identityStream, identityStorePassword, identityStorePassword);
344         }
345 
346         public Builder withIdentityMaterial(InputStream identityStream, char[] identityStorePassword, char[] identityPassword) {
347             return withIdentityMaterial(identityStream, identityStorePassword, identityPassword, KeyStore.getDefaultType());
348         }
349 
350         public Builder withIdentityMaterial(InputStream identityStream, char[] identityStorePassword, String identityStoreType) {
351             return withIdentityMaterial(identityStream, identityStorePassword, identityStorePassword, identityStoreType);
352         }
353 
354         public Builder withIdentityMaterial(InputStream identityStream, char[] identityStorePassword, char[] identityPassword, String identityStoreType) {
355             if (isNull(identityStream) || StringUtils.isBlank(identityStoreType)) {
356                 throw new GenericKeyStoreException(IDENTITY_VALIDATION_EXCEPTION_MESSAGE);
357             }
358 
359             KeyStore identity = KeyStoreUtils.loadKeyStore(identityStream, identityStorePassword, identityStoreType);
360             KeyStoreHolder identityHolder = new KeyStoreHolder(identity, identityStorePassword, identityPassword);
361             identities.add(identityHolder);
362             return this;
363         }
364 
365         public Builder withIdentityMaterial(KeyStore identityStore, char[] identityStorePassword) {
366             return withIdentityMaterial(identityStore, identityStorePassword, identityStorePassword);
367         }
368 
369         public Builder withIdentityMaterial(KeyStore identityStore, char[] identityStorePassword, char[] identityPassword) {
370             validateKeyStore(identityStore, IDENTITY_VALIDATION_EXCEPTION_MESSAGE);
371             KeyStoreHolder identityHolder = new KeyStoreHolder(identityStore, identityStorePassword, identityPassword);
372             identities.add(identityHolder);
373             return this;
374         }
375 
376         public Builder withIdentityMaterial(Key privateKey, char[] privateKeyPassword, Certificate... certificateChain) {
377             return withIdentityMaterial(privateKey, privateKeyPassword, null, certificateChain);
378         }
379 
380         public Builder withIdentityMaterial(Key privateKey, char[] privateKeyPassword, String alias, Certificate... certificateChain) {
381             KeyStore identityStore = KeyStoreUtils.createIdentityStore(privateKey, privateKeyPassword, alias, certificateChain);
382             identities.add(new KeyStoreHolder(identityStore, KeyStoreUtils.DUMMY_PASSWORD.toCharArray(), privateKeyPassword));
383             return this;
384         }
385 
386         public <T extends X509KeyManager> Builder withIdentityMaterial(T keyManager) {
387             identityManagers.add(KeyManagerUtils.wrapIfNeeded(keyManager));
388             return this;
389         }
390 
391         public <T extends KeyManagerFactory> Builder withIdentityMaterial(T keyManagerFactory) {
392             X509ExtendedKeyManager keyManager = KeyManagerUtils.getKeyManager(keyManagerFactory);
393             this.identityManagers.add(keyManager);
394             return this;
395         }
396 
397         /**
398          * Enables the possibility to swap the underlying KeyManager at runtime.
399          * After this option has been enabled the KeyManager can be swapped
400          * with {@link KeyManagerUtils#swapKeyManager(X509KeyManager, X509KeyManager) KeyManagerUtils#swapKeyManager(swappableKeyManager, newKeyManager)}
401          *
402          * @return {@link Builder}
403          */
404         public Builder withSwappableIdentityMaterial() {
405             swappableKeyManagerEnabled = true;
406             return this;
407         }
408 
409         private void validateKeyStore(KeyStore keyStore, String exceptionMessage) {
410             if (isNull(keyStore)) {
411                 throw new GenericKeyStoreException(exceptionMessage);
412             }
413         }
414 
415         public Builder withClientIdentityRoute(String clientAlias, String... hosts) {
416             return withClientIdentityRoute(
417                     clientAlias,
418                     Arrays.stream(hosts)
419                             .map(URI::create)
420                             .collect(Collectors.toList())
421             );
422         }
423 
424         public Builder withClientIdentityRoute(Map<String, List<String>> clientAliasesToHosts) {
425             clientAliasesToHosts.entrySet().stream()
426                     .map(clientAliasToHosts -> new AbstractMap.SimpleEntry<>(
427                             clientAliasToHosts.getKey(),
428                             clientAliasToHosts.getValue().stream()
429                                     .map(URI::create)
430                                     .collect(Collectors.toList())))
431                     .forEach(clientAliasToHosts -> withClientIdentityRoute(clientAliasToHosts.getKey(), clientAliasToHosts.getValue()));
432             return this;
433         }
434 
435         private Builder withClientIdentityRoute(String clientAlias, List<URI> hosts) {
436             if (StringUtils.isBlank(clientAlias)) {
437                 throw new IllegalArgumentException("clientAlias should be present");
438             }
439 
440             if (hosts.isEmpty()) {
441                 throw new IllegalArgumentException(String.format("At least one host should be present. No host(s) found for the given alias: [%s]", clientAlias));
442             }
443 
444             for (URI host : hosts) {
445                 UriUtils.validate(host);
446 
447                 if (preferredClientAliasToHost.containsKey(clientAlias)) {
448                     preferredClientAliasToHost.get(clientAlias).add(host);
449                 } else {
450                     preferredClientAliasToHost.put(clientAlias, new ArrayList<>(Collections.singletonList(host)));
451                 }
452             }
453             return this;
454         }
455 
456         public <T extends HostnameVerifier> Builder withHostnameVerifier(T hostnameVerifier) {
457             this.hostnameVerifier = hostnameVerifier;
458             return this;
459         }
460 
461         public Builder withCiphers(String... ciphers) {
462             sslParameters.setCipherSuites(ciphers);
463             return this;
464         }
465 
466         public Builder withProtocols(String... protocols) {
467             sslParameters.setProtocols(protocols);
468             return this;
469         }
470 
471         public Builder withNeedClientAuthentication() {
472             return withNeedClientAuthentication(true);
473         }
474 
475         public Builder withNeedClientAuthentication(boolean needClientAuthentication) {
476             sslParameters.setNeedClientAuth(needClientAuthentication);
477             return this;
478         }
479 
480         public Builder withWantClientAuthentication() {
481             return withWantClientAuthentication(true);
482         }
483 
484         public Builder withWantClientAuthentication(boolean wantClientAuthentication) {
485             sslParameters.setWantClientAuth(wantClientAuthentication);
486             return this;
487         }
488 
489         public Builder withSessionTimeout(int timeoutInSeconds) {
490             this.sessionTimeoutInSeconds = timeoutInSeconds;
491             return this;
492         }
493 
494         public Builder withSessionCacheSize(int cacheSizeInBytes) {
495             this.sessionCacheSizeInBytes = cacheSizeInBytes;
496             return this;
497         }
498 
499         public Builder withSslContextAlgorithm(String sslContextAlgorithm) {
500             this.sslContextAlgorithm = sslContextAlgorithm;
501             return this;
502         }
503 
504         public <T extends Provider> Builder withSecurityProvider(T securityProvider) {
505             this.securityProvider = securityProvider;
506             return this;
507         }
508 
509         public Builder withSecurityProvider(String securityProviderName) {
510             this.securityProviderName = securityProviderName;
511             return this;
512         }
513 
514         public <T extends SecureRandom> Builder withSecureRandom(T secureRandom) {
515             this.secureRandom = secureRandom;
516             return this;
517         }
518 
519         public Builder withTrustingAllCertificatesWithoutValidation() {
520             LOGGER.warn("UnsafeTrustManager is being used. Client/Server certificates will be accepted without validation.");
521             trustManagers.add(TrustManagerUtils.createUnsafeTrustManager());
522             return this;
523         }
524 
525         public Builder withPasswordCaching() {
526             passwordCachingEnabled = true;
527             return this;
528         }
529 
530         public SSLFactory build() {
531             if (!isIdentityMaterialPresent() && !isTrustMaterialPresent()) {
532                 throw new GenericSecurityException(IDENTITY_AND_TRUST_MATERIAL_VALIDATION_EXCEPTION_MESSAGE);
533             }
534 
535             X509ExtendedKeyManager keyManager = isIdentityMaterialPresent() ? createKeyManager() : null;
536             X509ExtendedTrustManager trustManager = isTrustMaterialPresent() ? createTrustManagers() : null;
537             SSLContext sslContext = SSLContextUtils.createSslContext(
538                     keyManager,
539                     trustManager,
540                     secureRandom,
541                     sslContextAlgorithm,
542                     securityProviderName,
543                     securityProvider
544             );
545 
546             if (sessionTimeoutInSeconds >= 0) {
547                 SSLSessionUtils.updateSessionTimeout(sslContext, sessionTimeoutInSeconds);
548             }
549 
550             if (sessionCacheSizeInBytes >= 0) {
551                 SSLSessionUtils.updateSessionCacheSize(sslContext, sessionCacheSizeInBytes);
552             }
553 
554             SSLParameters baseSslParameters = SSLParametersUtils.merge(sslParameters, sslContext.getDefaultSSLParameters());
555 
556             if (!passwordCachingEnabled && !identities.isEmpty()) {
557                 KeyStoreUtils.sanitizeKeyStores(identities);
558             }
559 
560             if (!passwordCachingEnabled && !trustStores.isEmpty()) {
561                 KeyStoreUtils.sanitizeKeyStores(trustStores);
562             }
563 
564             IdentityMaterial identityMaterial = new IdentityMaterial.Builder()
565                     .withKeyManager(keyManager)
566                     .withIdentities(Collections.unmodifiableList(identities))
567                     .build();
568 
569             TrustMaterial trustMaterial = new TrustMaterial.Builder()
570                     .withTrustManager(trustManager)
571                     .withTrustStores(Collections.unmodifiableList(trustStores))
572                     .build();
573 
574             SSLMaterial sslMaterial = new SSLMaterial.Builder()
575                     .withSslContext(sslContext)
576                     .withIdentityMaterial(identityMaterial)
577                     .withTrustMaterial(trustMaterial)
578                     .withSslParameters(baseSslParameters)
579                     .withHostnameVerifier(hostnameVerifier)
580                     .withCiphers(Collections.unmodifiableList(Arrays.asList(baseSslParameters.getCipherSuites())))
581                     .withProtocols(Collections.unmodifiableList(Arrays.asList(baseSslParameters.getProtocols())))
582                     .build();
583 
584             return new SSLFactory(sslMaterial);
585         }
586 
587         private boolean isTrustMaterialPresent() {
588             return !trustStores.isEmpty()
589                     || !trustManagers.isEmpty();
590         }
591 
592         private boolean isIdentityMaterialPresent() {
593             return !identities.isEmpty()
594                     || !identityManagers.isEmpty();
595         }
596 
597         private X509ExtendedKeyManager createKeyManager() {
598             return KeyManagerUtils.keyManagerBuilder()
599                     .withKeyManagers(identityManagers)
600                     .withIdentities(identities)
601                     .withSwappableKeyManager(swappableKeyManagerEnabled)
602                     .withClientAliasToHost(preferredClientAliasToHost)
603                     .build();
604         }
605 
606         private X509ExtendedTrustManager createTrustManagers() {
607             return TrustManagerUtils.trustManagerBuilder()
608                     .withTrustManagers(trustManagers)
609                     .withTrustStores(trustStores.stream()
610                             .map(KeyStoreHolder::getKeyStore)
611                             .collect(Collectors.toList()))
612                     .withSwappableTrustManager(swappableTrustManagerEnabled)
613                     .build();
614         }
615 
616     }
617 }