1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.ssl;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.net.Socket;
34 import java.net.URL;
35 import java.nio.file.Files;
36 import java.nio.file.OpenOption;
37 import java.nio.file.Path;
38 import java.security.KeyManagementException;
39 import java.security.KeyStore;
40 import java.security.KeyStoreException;
41 import java.security.NoSuchAlgorithmException;
42 import java.security.NoSuchProviderException;
43 import java.security.Principal;
44 import java.security.PrivateKey;
45 import java.security.Provider;
46 import java.security.SecureRandom;
47 import java.security.Security;
48 import java.security.UnrecoverableKeyException;
49 import java.security.cert.CertificateException;
50 import java.security.cert.X509Certificate;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.LinkedHashSet;
55 import java.util.Map;
56 import java.util.Set;
57
58 import javax.net.ssl.KeyManager;
59 import javax.net.ssl.KeyManagerFactory;
60 import javax.net.ssl.SSLContext;
61 import javax.net.ssl.SSLEngine;
62 import javax.net.ssl.SSLSocket;
63 import javax.net.ssl.TrustManager;
64 import javax.net.ssl.TrustManagerFactory;
65 import javax.net.ssl.X509ExtendedKeyManager;
66 import javax.net.ssl.X509TrustManager;
67
68 import org.apache.hc.core5.util.Args;
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class SSLContextBuilder {
83
84 static final String TLS = "TLS";
85
86 private String protocol;
87 private final Set<KeyManager> keyManagers;
88 private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
89 private String keyStoreType = KeyStore.getDefaultType();
90 private final Set<TrustManager> trustManagers;
91 private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
92 private SecureRandom secureRandom;
93 private Provider provider;
94 private Provider tsProvider;
95 private Provider ksProvider;
96
97
98
99
100 private static final KeyManager[] EMPTY_KEY_MANAGER_ARRAY = {};
101
102
103
104
105 private static final TrustManager[] EMPTY_TRUST_MANAGER_ARRAY = {};
106
107
108
109 public static SSLContextBuilder create() {
110 return new SSLContextBuilder();
111 }
112
113 public SSLContextBuilder() {
114 this.keyManagers = new LinkedHashSet<>();
115 this.trustManagers = new LinkedHashSet<>();
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public SSLContextBuilder setProtocol(final String protocol) {
133 this.protocol = protocol;
134 return this;
135 }
136
137 public SSLContextBuilder setProvider(final Provider provider) {
138 this.provider = provider;
139 return this;
140 }
141
142 public SSLContextBuilder setProvider(final String name) {
143 this.provider = Security.getProvider(name);
144 return this;
145 }
146
147
148
149
150
151
152
153 public SSLContextBuilder setTrustStoreProvider(final Provider provider) {
154 this.tsProvider = provider;
155 return this;
156 }
157
158
159
160
161
162
163
164 public SSLContextBuilder setTrustStoreProvider(final String name) throws NoSuchProviderException {
165 this.tsProvider = requireNonNullProvider(name);
166 return this;
167 }
168
169
170
171
172
173
174
175 public SSLContextBuilder setKeyStoreProvider(final Provider provider) {
176 this.ksProvider = provider;
177 return this;
178 }
179
180
181
182
183
184
185
186 public SSLContextBuilder setKeyStoreProvider(final String name) throws NoSuchProviderException {
187 this.ksProvider = requireNonNullProvider(name);
188 return this;
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 public SSLContextBuilder setKeyStoreType(final String keyStoreType) {
207 this.keyStoreType = keyStoreType;
208 return this;
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 public SSLContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFactoryAlgorithm) {
227 this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
228 return this;
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 public SSLContextBuilder setTrustManagerFactoryAlgorithm(final String trustManagerFactoryAlgorithm) {
247 this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm;
248 return this;
249 }
250
251 public SSLContextBuilder setSecureRandom(final SecureRandom secureRandom) {
252 this.secureRandom = secureRandom;
253 return this;
254 }
255
256
257
258
259
260
261 public SSLContextBuilder loadTrustMaterial(
262 final KeyStore trustStore,
263 final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
264
265 final String alg = trustManagerFactoryAlgorithm == null ?
266 TrustManagerFactory.getDefaultAlgorithm() : trustManagerFactoryAlgorithm;
267
268 final TrustManagerFactory tmFactory = tsProvider == null ?
269 TrustManagerFactory.getInstance(alg) : TrustManagerFactory.getInstance(alg, tsProvider);
270
271 tmFactory.init(trustStore);
272 final TrustManager[] tms = tmFactory.getTrustManagers();
273 if (tms != null) {
274 if (trustStrategy != null) {
275 for (int i = 0; i < tms.length; i++) {
276 final TrustManager tm = tms[i];
277 if (tm instanceof X509TrustManager) {
278 tms[i] = new TrustManagerDelegate((X509TrustManager) tm, trustStrategy);
279 }
280 }
281 }
282 Collections.addAll(this.trustManagers, tms);
283 }
284 return this;
285 }
286
287
288
289
290 public SSLContextBuilder loadTrustMaterial(
291 final Path file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
292 return loadTrustMaterial(file, null);
293 }
294
295
296
297
298 public SSLContextBuilder loadTrustMaterial(
299 final Path file,
300 final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
301 return loadTrustMaterial(file, storePassword, null);
302 }
303
304
305
306
307 public SSLContextBuilder loadTrustMaterial(
308 final Path file,
309 final char[] storePassword,
310 final TrustStrategy trustStrategy,
311 final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
312 Args.notNull(file, "Truststore file");
313 return loadTrustMaterial(loadKeyStore(file, storePassword, openOptions), trustStrategy);
314 }
315
316 public SSLContextBuilder loadTrustMaterial(
317 final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
318 return loadTrustMaterial(null, trustStrategy);
319 }
320
321 public SSLContextBuilder loadTrustMaterial(
322 final File file,
323 final char[] storePassword,
324 final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
325 Args.notNull(file, "Truststore file");
326 return loadTrustMaterial(file.toPath(), storePassword, trustStrategy);
327 }
328
329 public SSLContextBuilder loadTrustMaterial(
330 final File file,
331 final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
332 return loadTrustMaterial(file, storePassword, null);
333 }
334
335 public SSLContextBuilder loadTrustMaterial(
336 final File file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
337 return loadTrustMaterial(file, null);
338 }
339
340 public SSLContextBuilder loadTrustMaterial(
341 final URL url,
342 final char[] storePassword,
343 final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
344 Args.notNull(url, "Truststore URL");
345 return loadTrustMaterial(loadKeyStore(url, storePassword), trustStrategy);
346 }
347
348 public SSLContextBuilder loadTrustMaterial(
349 final URL url,
350 final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
351 return loadTrustMaterial(url, storePassword, null);
352 }
353
354 public SSLContextBuilder loadKeyMaterial(
355 final KeyStore keyStore,
356 final char[] keyPassword,
357 final PrivateKeyStrategy aliasStrategy)
358 throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
359
360 final String alg = keyManagerFactoryAlgorithm == null ?
361 KeyManagerFactory.getDefaultAlgorithm() : keyManagerFactoryAlgorithm;
362
363 final KeyManagerFactory kmFactory = ksProvider == null ?
364 KeyManagerFactory.getInstance(alg) : KeyManagerFactory.getInstance(alg, ksProvider);
365
366 kmFactory.init(keyStore, keyPassword);
367 final KeyManager[] kms = kmFactory.getKeyManagers();
368 if (kms != null) {
369 if (aliasStrategy != null) {
370 for (int i = 0; i < kms.length; i++) {
371 final KeyManager km = kms[i];
372 if (km instanceof X509ExtendedKeyManager) {
373 kms[i] = new KeyManagerDelegate((X509ExtendedKeyManager) km, aliasStrategy);
374 }
375 }
376 }
377 Collections.addAll(keyManagers, kms);
378 }
379 return this;
380 }
381
382
383
384
385 public SSLContextBuilder loadKeyMaterial(
386 final Path file,
387 final char[] storePassword,
388 final char[] keyPassword,
389 final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
390 return loadKeyMaterial(file, storePassword, keyPassword, null, openOptions);
391 }
392
393
394
395
396 public SSLContextBuilder loadKeyMaterial(
397 final Path file,
398 final char[] storePassword,
399 final char[] keyPassword,
400 final PrivateKeyStrategy aliasStrategy,
401 final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
402 Args.notNull(file, "Keystore file");
403 return loadKeyMaterial(loadKeyStore(file, storePassword, openOptions), keyPassword, aliasStrategy);
404 }
405
406 public SSLContextBuilder loadKeyMaterial(
407 final KeyStore keyStore,
408 final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
409 return loadKeyMaterial(keyStore, keyPassword, null);
410 }
411
412 public SSLContextBuilder loadKeyMaterial(
413 final File file,
414 final char[] storePassword,
415 final char[] keyPassword,
416 final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
417 Args.notNull(file, "Keystore file");
418 return loadKeyMaterial(file.toPath(), storePassword, keyPassword, aliasStrategy);
419 }
420
421 public SSLContextBuilder loadKeyMaterial(
422 final File file,
423 final char[] storePassword,
424 final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
425 return loadKeyMaterial(file, storePassword, keyPassword, null);
426 }
427
428 public SSLContextBuilder loadKeyMaterial(
429 final URL url,
430 final char[] storePassword,
431 final char[] keyPassword,
432 final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
433 Args.notNull(url, "Keystore URL");
434 return loadKeyMaterial(loadKeyStore(url, storePassword), keyPassword, aliasStrategy);
435 }
436
437 public SSLContextBuilder loadKeyMaterial(
438 final URL url,
439 final char[] storePassword,
440 final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
441 return loadKeyMaterial(url, storePassword, keyPassword, null);
442 }
443
444 protected void initSSLContext(
445 final SSLContext sslContext,
446 final Collection<KeyManager> keyManagers,
447 final Collection<TrustManager> trustManagers,
448 final SecureRandom secureRandom) throws KeyManagementException {
449 sslContext.init(
450 !keyManagers.isEmpty() ? keyManagers.toArray(EMPTY_KEY_MANAGER_ARRAY) : null,
451 !trustManagers.isEmpty() ? trustManagers.toArray(EMPTY_TRUST_MANAGER_ARRAY) : null,
452 secureRandom);
453 }
454
455 private KeyStore loadKeyStore(final Path file, final char[] password, final OpenOption... openOptions)
456 throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
457 final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
458 try (final InputStream inputStream = Files.newInputStream(file, openOptions)) {
459 keyStore.load(inputStream, password);
460 }
461 return keyStore;
462 }
463
464 private KeyStore loadKeyStore(final URL url, final char[] password)
465 throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
466 final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
467 try (final InputStream inputStream = url.openStream()) {
468 keyStore.load(inputStream, password);
469 }
470 return keyStore;
471 }
472
473 public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException {
474 final SSLContext sslContext;
475 final String protocolStr = this.protocol != null ? this.protocol : TLS;
476 if (this.provider != null) {
477 sslContext = SSLContext.getInstance(protocolStr, this.provider);
478 } else {
479 sslContext = SSLContext.getInstance(protocolStr);
480 }
481 initSSLContext(sslContext, keyManagers, trustManagers, secureRandom);
482 return sslContext;
483 }
484
485 static class TrustManagerDelegate implements X509TrustManager {
486
487 private final X509TrustManager trustManager;
488 private final TrustStrategy trustStrategy;
489
490 TrustManagerDelegate(final X509TrustManager trustManager, final TrustStrategy trustStrategy) {
491 this.trustManager = trustManager;
492 this.trustStrategy = trustStrategy;
493 }
494
495 @Override
496 public void checkClientTrusted(
497 final X509Certificate[] chain, final String authType) throws CertificateException {
498 this.trustManager.checkClientTrusted(chain, authType);
499 }
500
501 @Override
502 public void checkServerTrusted(
503 final X509Certificate[] chain, final String authType) throws CertificateException {
504 if (!this.trustStrategy.isTrusted(chain, authType)) {
505 this.trustManager.checkServerTrusted(chain, authType);
506 }
507 }
508
509 @Override
510 public X509Certificate[] getAcceptedIssuers() {
511 return this.trustManager.getAcceptedIssuers();
512 }
513
514 }
515
516 static class KeyManagerDelegate extends X509ExtendedKeyManager {
517
518 private final X509ExtendedKeyManager keyManager;
519 private final PrivateKeyStrategy aliasStrategy;
520
521 KeyManagerDelegate(final X509ExtendedKeyManager keyManager, final PrivateKeyStrategy aliasStrategy) {
522 this.keyManager = keyManager;
523 this.aliasStrategy = aliasStrategy;
524 }
525
526 @Override
527 public String[] getClientAliases(
528 final String keyType, final Principal[] issuers) {
529 return this.keyManager.getClientAliases(keyType, issuers);
530 }
531
532 public Map<String, PrivateKeyDetails> getClientAliasMap(
533 final String[] keyTypes, final Principal[] issuers) {
534 final Map<String, PrivateKeyDetails> validAliases = new HashMap<>();
535 for (final String keyType: keyTypes) {
536 putPrivateKeyDetails(validAliases, keyType, this.keyManager.getClientAliases(keyType, issuers));
537 }
538 return validAliases;
539 }
540
541 public Map<String, PrivateKeyDetails> getServerAliasMap(
542 final String keyType, final Principal[] issuers) {
543 final Map<String, PrivateKeyDetails> validAliases = new HashMap<>();
544 putPrivateKeyDetails(validAliases, keyType, this.keyManager.getServerAliases(keyType, issuers));
545 return validAliases;
546 }
547
548 private void putPrivateKeyDetails(final Map<String, PrivateKeyDetails> validAliases, final String keyType,
549 final String[] aliases) {
550 if (aliases != null) {
551 for (final String alias: aliases) {
552 validAliases.put(alias, new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias)));
553 }
554 }
555 }
556
557 @Override
558 public String chooseClientAlias(
559 final String[] keyTypes, final Principal[] issuers, final Socket socket) {
560 final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
561 return this.aliasStrategy.chooseAlias(validAliases,
562 socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null);
563 }
564
565 @Override
566 public String[] getServerAliases(
567 final String keyType, final Principal[] issuers) {
568 return this.keyManager.getServerAliases(keyType, issuers);
569 }
570
571 @Override
572 public String chooseServerAlias(
573 final String keyType, final Principal[] issuers, final Socket socket) {
574 final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
575 return this.aliasStrategy.chooseAlias(validAliases,
576 socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null);
577 }
578
579 @Override
580 public X509Certificate[] getCertificateChain(final String alias) {
581 return this.keyManager.getCertificateChain(alias);
582 }
583
584 @Override
585 public PrivateKey getPrivateKey(final String alias) {
586 return this.keyManager.getPrivateKey(alias);
587 }
588
589 @Override
590 public String chooseEngineClientAlias(
591 final String[] keyTypes, final Principal[] issuers, final SSLEngine sslEngine) {
592 final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
593 return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters());
594 }
595
596 @Override
597 public String chooseEngineServerAlias(
598 final String keyType, final Principal[] issuers, final SSLEngine sslEngine) {
599 final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
600 return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters());
601 }
602
603 }
604
605 private Provider requireNonNullProvider(final String name) throws NoSuchProviderException {
606 final Provider provider = Security.getProvider(name);
607 if (provider == null) {
608 throw new NoSuchProviderException(name);
609 }
610 return provider;
611 }
612
613 @Override
614 public String toString() {
615 return "[provider=" + provider + ", protocol=" + protocol + ", keyStoreType=" + keyStoreType
616 + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyManagers=" + keyManagers
617 + ", trustManagerFactoryAlgorithm=" + trustManagerFactoryAlgorithm + ", trustManagers=" + trustManagers
618 + ", secureRandom=" + secureRandom + "]";
619 }
620
621 }