View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.ssl;
29  
30  import java.io.File;
31  import java.io.FileInputStream;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.net.Socket;
35  import java.net.URL;
36  import java.security.KeyManagementException;
37  import java.security.KeyStore;
38  import java.security.KeyStoreException;
39  import java.security.NoSuchAlgorithmException;
40  import java.security.Principal;
41  import java.security.PrivateKey;
42  import java.security.Provider;
43  import java.security.SecureRandom;
44  import java.security.Security;
45  import java.security.UnrecoverableKeyException;
46  import java.security.cert.CertificateException;
47  import java.security.cert.X509Certificate;
48  import java.util.Collection;
49  import java.util.HashMap;
50  import java.util.LinkedHashSet;
51  import java.util.Map;
52  import java.util.Set;
53  
54  import javax.net.ssl.KeyManager;
55  import javax.net.ssl.KeyManagerFactory;
56  import javax.net.ssl.SSLContext;
57  import javax.net.ssl.SSLEngine;
58  import javax.net.ssl.SSLSocket;
59  import javax.net.ssl.TrustManager;
60  import javax.net.ssl.TrustManagerFactory;
61  import javax.net.ssl.X509ExtendedKeyManager;
62  import javax.net.ssl.X509TrustManager;
63  
64  import org.apache.hc.core5.util.Args;
65  
66  /**
67   * Builder for {@link javax.net.ssl.SSLContext} instances.
68   * <p>
69   * Please note: the default Oracle JSSE implementation of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
70   * accepts multiple key and trust managers, however only only first matching type is ever used.
71   * See for example:
72   * <a href="http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLContext.html#init%28javax.net.ssl.KeyManager[],%20javax.net.ssl.TrustManager[],%20java.security.SecureRandom%29">
73   * SSLContext.html#init
74   * </a>
75   *
76   * @since 4.4
77   */
78  public class SSLContextBuilder {
79  
80      static final String TLS   = "TLS";
81  
82      private String protocol;
83      private final Set<KeyManager> keyManagers;
84      private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
85      private String keyStoreType = KeyStore.getDefaultType();
86      private final Set<TrustManager> trustManagers;
87      private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
88      private SecureRandom secureRandom;
89      private Provider provider;
90  
91      public static SSLContextBuilder create() {
92          return new SSLContextBuilder();
93      }
94  
95      public SSLContextBuilder() {
96          super();
97          this.keyManagers = new LinkedHashSet<>();
98          this.trustManagers = new LinkedHashSet<>();
99      }
100 
101     /**
102      * Sets the SSLContext algorithm name.
103      *
104      * @param protocol
105      *            the SSLContext algorithm name of the requested protocol. See
106      *            the SSLContext section in the <a href=
107      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
108      *            Cryptography Architecture Standard Algorithm Name
109      *            Documentation</a> for more information.
110      * @return this builder
111      * @see <a href=
112      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
113      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
114      */
115     public SSLContextBuilder setProtocol(final String protocol) {
116         this.protocol = protocol;
117         return this;
118     }
119 
120     public SSLContextBuilder setProvider(final Provider provider) {
121         this.provider = provider;
122         return this;
123     }
124 
125     public SSLContextBuilder setProvider(final String name) {
126         this.provider = Security.getProvider(name);
127         return this;
128     }
129 
130     /**
131      * Sets the key store type.
132      *
133      * @param keyStoreType
134      *            the SSLkey store type. See
135      *            the KeyStore section in the <a href=
136      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java
137      *            Cryptography Architecture Standard Algorithm Name
138      *            Documentation</a> for more information.
139      * @return this builder
140      * @see <a href=
141      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java
142      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
143      * @since 4.4.7
144      */
145     public SSLContextBuilder setKeyStoreType(final String keyStoreType) {
146         this.keyStoreType = keyStoreType;
147         return this;
148     }
149 
150     /**
151      * Sets the key manager factory algorithm name.
152      *
153      * @param keyManagerFactoryAlgorithm
154      *            the key manager factory algorithm name of the requested protocol. See
155      *            the KeyManagerFactory section in the <a href=
156      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java
157      *            Cryptography Architecture Standard Algorithm Name
158      *            Documentation</a> for more information.
159      * @return this builder
160      * @see <a href=
161      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java
162      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
163      * @since 4.4.7
164      */
165     public SSLContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFactoryAlgorithm) {
166         this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
167         return this;
168     }
169 
170     /**
171      * Sets the trust manager factory algorithm name.
172      *
173      * @param trustManagerFactoryAlgorithm
174      *            the trust manager algorithm name of the requested protocol. See
175      *            the TrustManagerFactory section in the <a href=
176      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java
177      *            Cryptography Architecture Standard Algorithm Name
178      *            Documentation</a> for more information.
179      * @return this builder
180      * @see <a href=
181      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java
182      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
183      * @since 4.4.7
184      */
185     public SSLContextBuilder setTrustManagerFactoryAlgorithm(final String trustManagerFactoryAlgorithm) {
186         this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm;
187         return this;
188     }
189 
190     public SSLContextBuilder setSecureRandom(final SecureRandom secureRandom) {
191         this.secureRandom = secureRandom;
192         return this;
193     }
194 
195     public SSLContextBuilder loadTrustMaterial(
196             final KeyStore truststore,
197             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
198         final TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
199                 trustManagerFactoryAlgorithm == null ? TrustManagerFactory.getDefaultAlgorithm()
200                         : trustManagerFactoryAlgorithm);
201         tmfactory.init(truststore);
202         final TrustManager[] tms = tmfactory.getTrustManagers();
203         if (tms != null) {
204             if (trustStrategy != null) {
205                 for (int i = 0; i < tms.length; i++) {
206                     final TrustManager tm = tms[i];
207                     if (tm instanceof X509TrustManager) {
208                         tms[i] = new TrustManagerDelegate(
209                                 (X509TrustManager) tm, trustStrategy);
210                     }
211                 }
212             }
213             for (final TrustManager tm : tms) {
214                 this.trustManagers.add(tm);
215             }
216         }
217         return this;
218     }
219 
220     public SSLContextBuilder loadTrustMaterial(
221             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
222         return loadTrustMaterial(null, trustStrategy);
223     }
224 
225     public SSLContextBuilder loadTrustMaterial(
226             final File file,
227             final char[] storePassword,
228             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
229         Args.notNull(file, "Truststore file");
230         final KeyStore trustStore = KeyStore.getInstance(keyStoreType);
231         try (final FileInputStream inStream = new FileInputStream(file)) {
232             trustStore.load(inStream, storePassword);
233         }
234         return loadTrustMaterial(trustStore, trustStrategy);
235     }
236 
237     public SSLContextBuilder loadTrustMaterial(
238             final File file,
239             final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
240         return loadTrustMaterial(file, storePassword, null);
241     }
242 
243     public SSLContextBuilder loadTrustMaterial(
244             final File file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
245         return loadTrustMaterial(file, null);
246     }
247 
248     public SSLContextBuilder loadTrustMaterial(
249             final URL url,
250             final char[] storePassword,
251             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
252         Args.notNull(url, "Truststore URL");
253         final KeyStore trustStore = KeyStore.getInstance(keyStoreType);
254         try (final InputStream inStream = url.openStream()) {
255             trustStore.load(inStream, storePassword);
256         }
257         return loadTrustMaterial(trustStore, trustStrategy);
258     }
259 
260     public SSLContextBuilder loadTrustMaterial(
261             final URL url,
262             final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
263         return loadTrustMaterial(url, storePassword, null);
264     }
265 
266     public SSLContextBuilder loadKeyMaterial(
267             final KeyStore keystore,
268             final char[] keyPassword,
269             final PrivateKeyStrategy aliasStrategy)
270             throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
271         final KeyManagerFactory kmfactory = KeyManagerFactory
272                 .getInstance(keyManagerFactoryAlgorithm == null ? KeyManagerFactory.getDefaultAlgorithm()
273                         : keyManagerFactoryAlgorithm);
274         kmfactory.init(keystore, keyPassword);
275         final KeyManager[] kms = kmfactory.getKeyManagers();
276         if (kms != null) {
277             if (aliasStrategy != null) {
278                 for (int i = 0; i < kms.length; i++) {
279                     final KeyManager km = kms[i];
280                     if (km instanceof X509ExtendedKeyManager) {
281                         kms[i] = new KeyManagerDelegate((X509ExtendedKeyManager) km, aliasStrategy);
282                     }
283                 }
284             }
285             for (final KeyManager km : kms) {
286                 keyManagers.add(km);
287             }
288         }
289         return this;
290     }
291 
292     public SSLContextBuilder loadKeyMaterial(
293             final KeyStore keystore,
294             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
295         return loadKeyMaterial(keystore, keyPassword, null);
296     }
297 
298     public SSLContextBuilder loadKeyMaterial(
299             final File file,
300             final char[] storePassword,
301             final char[] keyPassword,
302             final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
303         Args.notNull(file, "Keystore file");
304         final KeyStore identityStore = KeyStore.getInstance(keyStoreType);
305         try (final FileInputStream inStream = new FileInputStream(file)) {
306             identityStore.load(inStream, storePassword);
307         }
308         return loadKeyMaterial(identityStore, keyPassword, aliasStrategy);
309     }
310 
311     public SSLContextBuilder loadKeyMaterial(
312             final File file,
313             final char[] storePassword,
314             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
315         return loadKeyMaterial(file, storePassword, keyPassword, null);
316     }
317 
318     public SSLContextBuilder loadKeyMaterial(
319             final URL url,
320             final char[] storePassword,
321             final char[] keyPassword,
322             final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
323         Args.notNull(url, "Keystore URL");
324         final KeyStore identityStore = KeyStore.getInstance(keyStoreType);
325         try (final InputStream inStream = url.openStream()) {
326             identityStore.load(inStream, storePassword);
327         }
328         return loadKeyMaterial(identityStore, keyPassword, aliasStrategy);
329     }
330 
331     public SSLContextBuilder loadKeyMaterial(
332             final URL url,
333             final char[] storePassword,
334             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
335         return loadKeyMaterial(url, storePassword, keyPassword, null);
336     }
337 
338     protected void initSSLContext(
339             final SSLContext sslContext,
340             final Collection<KeyManager> keyManagers,
341             final Collection<TrustManager> trustManagers,
342             final SecureRandom secureRandom) throws KeyManagementException {
343         sslContext.init(
344                 !keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null,
345                 !trustManagers.isEmpty() ? trustManagers.toArray(new TrustManager[trustManagers.size()]) : null,
346                 secureRandom);
347     }
348 
349     public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException {
350         final SSLContext sslContext;
351         final String protocolStr = this.protocol != null ? this.protocol : TLS;
352         if (this.provider != null) {
353             sslContext = SSLContext.getInstance(protocolStr, this.provider);
354         } else {
355             sslContext = SSLContext.getInstance(protocolStr);
356         }
357         initSSLContext(sslContext, keyManagers, trustManagers, secureRandom);
358         return sslContext;
359     }
360 
361     static class TrustManagerDelegate implements X509TrustManager {
362 
363         private final X509TrustManager trustManager;
364         private final TrustStrategy trustStrategy;
365 
366         TrustManagerDelegate(final X509TrustManager trustManager, final TrustStrategy trustStrategy) {
367             super();
368             this.trustManager = trustManager;
369             this.trustStrategy = trustStrategy;
370         }
371 
372         @Override
373         public void checkClientTrusted(
374                 final X509Certificate[] chain, final String authType) throws CertificateException {
375             this.trustManager.checkClientTrusted(chain, authType);
376         }
377 
378         @Override
379         public void checkServerTrusted(
380                 final X509Certificate[] chain, final String authType) throws CertificateException {
381             if (!this.trustStrategy.isTrusted(chain, authType)) {
382                 this.trustManager.checkServerTrusted(chain, authType);
383             }
384         }
385 
386         @Override
387         public X509Certificate[] getAcceptedIssuers() {
388             return this.trustManager.getAcceptedIssuers();
389         }
390 
391     }
392 
393     static class KeyManagerDelegate extends X509ExtendedKeyManager {
394 
395         private final X509ExtendedKeyManager keyManager;
396         private final PrivateKeyStrategy aliasStrategy;
397 
398         KeyManagerDelegate(final X509ExtendedKeyManager keyManager, final PrivateKeyStrategy aliasStrategy) {
399             super();
400             this.keyManager = keyManager;
401             this.aliasStrategy = aliasStrategy;
402         }
403 
404         @Override
405         public String[] getClientAliases(
406                 final String keyType, final Principal[] issuers) {
407             return this.keyManager.getClientAliases(keyType, issuers);
408         }
409 
410         public Map<String, PrivateKeyDetails> getClientAliasMap(
411                 final String[] keyTypes, final Principal[] issuers) {
412             final Map<String, PrivateKeyDetails> validAliases = new HashMap<>();
413             for (final String keyType: keyTypes) {
414                 final String[] aliases = this.keyManager.getClientAliases(keyType, issuers);
415                 if (aliases != null) {
416                     for (final String alias: aliases) {
417                         validAliases.put(alias,
418                                 new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias)));
419                     }
420                 }
421             }
422             return validAliases;
423         }
424 
425         public Map<String, PrivateKeyDetails> getServerAliasMap(
426                 final String keyType, final Principal[] issuers) {
427             final Map<String, PrivateKeyDetails> validAliases = new HashMap<>();
428             final String[] aliases = this.keyManager.getServerAliases(keyType, issuers);
429             if (aliases != null) {
430                 for (final String alias: aliases) {
431                     validAliases.put(alias,
432                             new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias)));
433                 }
434             }
435             return validAliases;
436         }
437 
438         @Override
439         public String chooseClientAlias(
440                 final String[] keyTypes, final Principal[] issuers, final Socket socket) {
441             final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
442             return this.aliasStrategy.chooseAlias(validAliases,
443                     socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null);
444         }
445 
446         @Override
447         public String[] getServerAliases(
448                 final String keyType, final Principal[] issuers) {
449             return this.keyManager.getServerAliases(keyType, issuers);
450         }
451 
452         @Override
453         public String chooseServerAlias(
454                 final String keyType, final Principal[] issuers, final Socket socket) {
455             final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
456             return this.aliasStrategy.chooseAlias(validAliases,
457                     socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null);
458         }
459 
460         @Override
461         public X509Certificate[] getCertificateChain(final String alias) {
462             return this.keyManager.getCertificateChain(alias);
463         }
464 
465         @Override
466         public PrivateKey getPrivateKey(final String alias) {
467             return this.keyManager.getPrivateKey(alias);
468         }
469 
470         @Override
471         public String chooseEngineClientAlias(
472                 final String[] keyTypes, final Principal[] issuers, final SSLEngine sslEngine) {
473             final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
474             return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters());
475         }
476 
477         @Override
478         public String chooseEngineServerAlias(
479                 final String keyType, final Principal[] issuers, final SSLEngine sslEngine) {
480             final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
481             return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters());
482         }
483 
484     }
485 
486     @Override
487     public String toString() {
488         return "[provider=" + provider + ", protocol=" + protocol + ", keyStoreType=" + keyStoreType
489                 + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyManagers=" + keyManagers
490                 + ", trustManagerFactoryAlgorithm=" + trustManagerFactoryAlgorithm + ", trustManagers=" + trustManagers
491                 + ", secureRandom=" + secureRandom + "]";
492     }
493 }