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.http.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.TrustManager;
59  import javax.net.ssl.TrustManagerFactory;
60  import javax.net.ssl.X509ExtendedKeyManager;
61  import javax.net.ssl.X509TrustManager;
62  
63  import org.apache.http.util.Args;
64  
65  /**
66   * Builder for {@link javax.net.ssl.SSLContext} instances.
67   * <p>
68   * Please note: the default Oracle JSSE implementation of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
69   * accepts multiple key and trust managers, however only only first matching type is ever used.
70   * See for example:
71   * <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">
72   * SSLContext.html#init
73   * </a>
74   * <p>
75   * TODO Specify which Oracle JSSE versions the above has been verified.
76   *  </p>
77   * @since 4.4
78   */
79  public class SSLContextBuilder {
80  
81      static final String TLS   = "TLS";
82  
83      private String protocol;
84      private final Set<KeyManager> keyManagers;
85      private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
86      private String keyStoreType = KeyStore.getDefaultType();
87      private final Set<TrustManager> trustManagers;
88      private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
89      private SecureRandom secureRandom;
90      private Provider provider;
91  
92      public static SSLContextBuilder create() {
93          return new SSLContextBuilder();
94      }
95  
96      public SSLContextBuilder() {
97          super();
98          this.keyManagers = new LinkedHashSet<KeyManager>();
99          this.trustManagers = new LinkedHashSet<TrustManager>();
100     }
101 
102     /**
103      * Sets the SSLContext protocol algorithm name.
104      *
105      * @param protocol
106      *            the SSLContext protocol algorithm name of the requested protocol. See
107      *            the SSLContext section in the <a href=
108      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
109      *            Cryptography Architecture Standard Algorithm Name
110      *            Documentation</a> for more information.
111      * @return this builder
112      * @see <a href=
113      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
114      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
115      * @deprecated Use {@link #setProtocol(String)}.
116      */
117     @Deprecated
118     public SSLContextBuilder useProtocol(final String protocol) {
119         this.protocol = protocol;
120         return this;
121     }
122 
123     /**
124      * Sets the SSLContext protocol algorithm name.
125      *
126      * @param protocol
127      *            the SSLContext protocol algorithm name of the requested protocol. See
128      *            the SSLContext section in the <a href=
129      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
130      *            Cryptography Architecture Standard Algorithm Name
131      *            Documentation</a> for more information.
132      * @return this builder
133      * @see <a href=
134      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java
135      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
136      * @since 4.4.7
137      */
138     public SSLContextBuilder setProtocol(final String protocol) {
139         this.protocol = protocol;
140         return this;
141     }
142 
143     public SSLContextBuilder setSecureRandom(final SecureRandom secureRandom) {
144         this.secureRandom = secureRandom;
145         return this;
146     }
147 
148     public SSLContextBuilder setProvider(final Provider provider) {
149         this.provider = provider;
150         return this;
151     }
152 
153     public SSLContextBuilder setProvider(final String name) {
154         this.provider = Security.getProvider(name);
155         return this;
156     }
157 
158     /**
159      * Sets the key store type.
160      *
161      * @param keyStoreType
162      *            the SSLkey store type. See
163      *            the KeyStore section in the <a href=
164      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java
165      *            Cryptography Architecture Standard Algorithm Name
166      *            Documentation</a> for more information.
167      * @return this builder
168      * @see <a href=
169      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java
170      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
171      * @since 4.4.7
172      */
173     public SSLContextBuilder setKeyStoreType(final String keyStoreType) {
174         this.keyStoreType = keyStoreType;
175         return this;
176     }
177 
178     /**
179      * Sets the key manager factory algorithm name.
180      *
181      * @param keyManagerFactoryAlgorithm
182      *            the key manager factory algorithm name of the requested protocol. See
183      *            the KeyManagerFactory section in the <a href=
184      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java
185      *            Cryptography Architecture Standard Algorithm Name
186      *            Documentation</a> for more information.
187      * @return this builder
188      * @see <a href=
189      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java
190      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
191      * @since 4.4.7
192      */
193     public SSLContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFactoryAlgorithm) {
194         this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
195         return this;
196     }
197 
198     /**
199      * Sets the trust manager factory algorithm name.
200      *
201      * @param trustManagerFactoryAlgorithm
202      *            the trust manager algorithm name of the requested protocol. See
203      *            the TrustManagerFactory section in the <a href=
204      *            "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java
205      *            Cryptography Architecture Standard Algorithm Name
206      *            Documentation</a> for more information.
207      * @return this builder
208      * @see <a href=
209      *      "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java
210      *      Cryptography Architecture Standard Algorithm Name Documentation</a>
211      * @since 4.4.7
212      */
213     public SSLContextBuilder setTrustManagerFactoryAlgorithm(final String trustManagerFactoryAlgorithm) {
214         this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm;
215         return this;
216     }
217 
218     public SSLContextBuilder loadTrustMaterial(
219             final KeyStore truststore,
220             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
221         final TrustManagerFactory tmfactory = TrustManagerFactory
222                 .getInstance(trustManagerFactoryAlgorithm == null ? TrustManagerFactory.getDefaultAlgorithm()
223                         : trustManagerFactoryAlgorithm);
224         tmfactory.init(truststore);
225         final TrustManager[] tms = tmfactory.getTrustManagers();
226         if (tms != null) {
227             if (trustStrategy != null) {
228                 for (int i = 0; i < tms.length; i++) {
229                     final TrustManager tm = tms[i];
230                     if (tm instanceof X509TrustManager) {
231                         tms[i] = new TrustManagerDelegate((X509TrustManager) tm, trustStrategy);
232                     }
233                 }
234             }
235             for (final TrustManager tm : tms) {
236                 this.trustManagers.add(tm);
237             }
238         }
239         return this;
240     }
241 
242     public SSLContextBuilder loadTrustMaterial(
243             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
244         return loadTrustMaterial(null, trustStrategy);
245     }
246 
247     public SSLContextBuilder loadTrustMaterial(
248             final File file,
249             final char[] storePassword,
250             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
251         Args.notNull(file, "Truststore file");
252         final KeyStore trustStore = KeyStore.getInstance(keyStoreType);
253         final FileInputStream instream = new FileInputStream(file);
254         try {
255             trustStore.load(instream, storePassword);
256         } finally {
257             instream.close();
258         }
259         return loadTrustMaterial(trustStore, trustStrategy);
260     }
261 
262     public SSLContextBuilder loadTrustMaterial(
263             final File file,
264             final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
265         return loadTrustMaterial(file, storePassword, null);
266     }
267 
268     public SSLContextBuilder loadTrustMaterial(
269             final File file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
270         return loadTrustMaterial(file, null);
271     }
272 
273     public SSLContextBuilder loadTrustMaterial(
274             final URL url,
275             final char[] storePassword,
276             final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
277         Args.notNull(url, "Truststore URL");
278         final KeyStore trustStore = KeyStore.getInstance(keyStoreType);
279         final InputStream instream = url.openStream();
280         try {
281             trustStore.load(instream, storePassword);
282         } finally {
283             instream.close();
284         }
285         return loadTrustMaterial(trustStore, trustStrategy);
286     }
287 
288     public SSLContextBuilder loadTrustMaterial(
289             final URL url,
290             final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
291         return loadTrustMaterial(url, storePassword, null);
292     }
293 
294     public SSLContextBuilder loadKeyMaterial(
295             final KeyStore keystore,
296             final char[] keyPassword,
297             final PrivateKeyStrategy aliasStrategy)
298             throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
299         final KeyManagerFactory kmfactory = KeyManagerFactory
300                 .getInstance(keyManagerFactoryAlgorithm == null ? KeyManagerFactory.getDefaultAlgorithm()
301                         : keyManagerFactoryAlgorithm);
302         kmfactory.init(keystore, keyPassword);
303         final KeyManager[] kms = kmfactory.getKeyManagers();
304         if (kms != null) {
305             if (aliasStrategy != null) {
306                 for (int i = 0; i < kms.length; i++) {
307                     final KeyManager km = kms[i];
308                     if (km instanceof X509ExtendedKeyManager) {
309                         kms[i] = new KeyManagerDelegate((X509ExtendedKeyManager) km, aliasStrategy);
310                     }
311                 }
312             }
313             for (final KeyManager km : kms) {
314                 keyManagers.add(km);
315             }
316         }
317         return this;
318     }
319 
320     public SSLContextBuilder loadKeyMaterial(
321             final KeyStore keystore,
322             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
323         return loadKeyMaterial(keystore, keyPassword, null);
324     }
325 
326     public SSLContextBuilder loadKeyMaterial(
327             final File file,
328             final char[] storePassword,
329             final char[] keyPassword,
330             final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
331         Args.notNull(file, "Keystore file");
332         final KeyStore identityStore = KeyStore.getInstance(keyStoreType);
333         final FileInputStream instream = new FileInputStream(file);
334         try {
335             identityStore.load(instream, storePassword);
336         } finally {
337             instream.close();
338         }
339         return loadKeyMaterial(identityStore, keyPassword, aliasStrategy);
340     }
341 
342     public SSLContextBuilder loadKeyMaterial(
343             final File file,
344             final char[] storePassword,
345             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
346         return loadKeyMaterial(file, storePassword, keyPassword, null);
347     }
348 
349     public SSLContextBuilder loadKeyMaterial(
350             final URL url,
351             final char[] storePassword,
352             final char[] keyPassword,
353             final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
354         Args.notNull(url, "Keystore URL");
355         final KeyStore identityStore = KeyStore.getInstance(keyStoreType);
356         final InputStream instream = url.openStream();
357         try {
358             identityStore.load(instream, storePassword);
359         } finally {
360             instream.close();
361         }
362         return loadKeyMaterial(identityStore, keyPassword, aliasStrategy);
363     }
364 
365     public SSLContextBuilder loadKeyMaterial(
366             final URL url,
367             final char[] storePassword,
368             final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
369         return loadKeyMaterial(url, storePassword, keyPassword, null);
370     }
371 
372     protected void initSSLContext(
373             final SSLContext sslContext,
374             final Collection<KeyManager> keyManagers,
375             final Collection<TrustManager> trustManagers,
376             final SecureRandom secureRandom) throws KeyManagementException {
377         sslContext.init(
378                 !keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null,
379                 !trustManagers.isEmpty() ? trustManagers.toArray(new TrustManager[trustManagers.size()]) : null,
380                 secureRandom);
381     }
382 
383     public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException {
384         final SSLContext sslContext;
385         final String protocolStr = this.protocol != null ? this.protocol : TLS;
386         if (this.provider != null) {
387             sslContext = SSLContext.getInstance(protocolStr, this.provider);
388         } else {
389             sslContext = SSLContext.getInstance(protocolStr);
390         }
391         initSSLContext(sslContext, keyManagers, trustManagers, secureRandom);
392         return sslContext;
393     }
394 
395     static class TrustManagerDelegate implements X509TrustManager {
396 
397         private final X509TrustManager trustManager;
398         private final TrustStrategy trustStrategy;
399 
400         TrustManagerDelegate(final X509TrustManager trustManager, final TrustStrategy trustStrategy) {
401             super();
402             this.trustManager = trustManager;
403             this.trustStrategy = trustStrategy;
404         }
405 
406         @Override
407         public void checkClientTrusted(
408                 final X509Certificate[] chain, final String authType) throws CertificateException {
409             this.trustManager.checkClientTrusted(chain, authType);
410         }
411 
412         @Override
413         public void checkServerTrusted(
414                 final X509Certificate[] chain, final String authType) throws CertificateException {
415             if (!this.trustStrategy.isTrusted(chain, authType)) {
416                 this.trustManager.checkServerTrusted(chain, authType);
417             }
418         }
419 
420         @Override
421         public X509Certificate[] getAcceptedIssuers() {
422             return this.trustManager.getAcceptedIssuers();
423         }
424 
425     }
426 
427     static class KeyManagerDelegate extends X509ExtendedKeyManager {
428 
429         private final X509ExtendedKeyManager keyManager;
430         private final PrivateKeyStrategy aliasStrategy;
431 
432         KeyManagerDelegate(final X509ExtendedKeyManager keyManager, final PrivateKeyStrategy aliasStrategy) {
433             super();
434             this.keyManager = keyManager;
435             this.aliasStrategy = aliasStrategy;
436         }
437 
438         @Override
439         public String[] getClientAliases(
440                 final String keyType, final Principal[] issuers) {
441             return this.keyManager.getClientAliases(keyType, issuers);
442         }
443 
444         public Map<String, PrivateKeyDetails> getClientAliasMap(
445                 final String[] keyTypes, final Principal[] issuers) {
446             final Map<String, PrivateKeyDetails> validAliases = new HashMap<String, PrivateKeyDetails>();
447             for (final String keyType: keyTypes) {
448                 final String[] aliases = this.keyManager.getClientAliases(keyType, issuers);
449                 if (aliases != null) {
450                     for (final String alias: aliases) {
451                         validAliases.put(alias,
452                                 new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias)));
453                     }
454                 }
455             }
456             return validAliases;
457         }
458 
459         public Map<String, PrivateKeyDetails> getServerAliasMap(
460                 final String keyType, final Principal[] issuers) {
461             final Map<String, PrivateKeyDetails> validAliases = new HashMap<String, PrivateKeyDetails>();
462             final String[] aliases = this.keyManager.getServerAliases(keyType, issuers);
463             if (aliases != null) {
464                 for (final String alias: aliases) {
465                     validAliases.put(alias,
466                             new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias)));
467                 }
468             }
469             return validAliases;
470         }
471 
472         @Override
473         public String chooseClientAlias(
474                 final String[] keyTypes, final Principal[] issuers, final Socket socket) {
475             final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
476             return this.aliasStrategy.chooseAlias(validAliases, socket);
477         }
478 
479         @Override
480         public String[] getServerAliases(
481                 final String keyType, final Principal[] issuers) {
482             return this.keyManager.getServerAliases(keyType, issuers);
483         }
484 
485         @Override
486         public String chooseServerAlias(
487                 final String keyType, final Principal[] issuers, final Socket socket) {
488             final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
489             return this.aliasStrategy.chooseAlias(validAliases, socket);
490         }
491 
492         @Override
493         public X509Certificate[] getCertificateChain(final String alias) {
494             return this.keyManager.getCertificateChain(alias);
495         }
496 
497         @Override
498         public PrivateKey getPrivateKey(final String alias) {
499             return this.keyManager.getPrivateKey(alias);
500         }
501 
502         @Override
503         public String chooseEngineClientAlias(
504                 final String[] keyTypes, final Principal[] issuers, final SSLEngine sslEngine) {
505             final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers);
506             return this.aliasStrategy.chooseAlias(validAliases, null);
507         }
508 
509         @Override
510         public String chooseEngineServerAlias(
511                 final String keyType, final Principal[] issuers, final SSLEngine sslEngine) {
512             final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers);
513             return this.aliasStrategy.chooseAlias(validAliases, null);
514         }
515 
516     }
517 
518     @Override
519     public String toString() {
520         return "[provider=" + provider + ", protocol=" + protocol + ", keyStoreType=" + keyStoreType
521                 + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyManagers=" + keyManagers
522                 + ", trustManagerFactoryAlgorithm=" + trustManagerFactoryAlgorithm + ", trustManagers=" + trustManagers
523                 + ", secureRandom=" + secureRandom + "]";
524     }
525 
526 }