001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.ssl;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.net.Socket;
035import java.net.URL;
036import java.security.KeyManagementException;
037import java.security.KeyStore;
038import java.security.KeyStoreException;
039import java.security.NoSuchAlgorithmException;
040import java.security.Principal;
041import java.security.PrivateKey;
042import java.security.Provider;
043import java.security.SecureRandom;
044import java.security.Security;
045import java.security.UnrecoverableKeyException;
046import java.security.cert.CertificateException;
047import java.security.cert.X509Certificate;
048import java.util.Collection;
049import java.util.HashMap;
050import java.util.LinkedHashSet;
051import java.util.Map;
052import java.util.Set;
053
054import javax.net.ssl.KeyManager;
055import javax.net.ssl.KeyManagerFactory;
056import javax.net.ssl.SSLContext;
057import javax.net.ssl.SSLEngine;
058import javax.net.ssl.TrustManager;
059import javax.net.ssl.TrustManagerFactory;
060import javax.net.ssl.X509ExtendedKeyManager;
061import javax.net.ssl.X509TrustManager;
062
063import org.apache.http.util.Args;
064
065/**
066 * Builder for {@link javax.net.ssl.SSLContext} instances.
067 * <p>
068 * Please note: the default Oracle JSSE implementation of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
069 * accepts multiple key and trust managers, however only only first matching type is ever used.
070 * See for example:
071 * <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">
072 * SSLContext.html#init
073 * </a>
074 * <p>
075 * TODO Specify which Oracle JSSE versions the above has been verified.
076 *  </p>
077 * @since 4.4
078 */
079public class SSLContextBuilder {
080
081    static final String TLS   = "TLS";
082
083    private String protocol;
084    private final Set<KeyManager> keyManagers;
085    private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
086    private String keyStoreType = KeyStore.getDefaultType();
087    private final Set<TrustManager> trustManagers;
088    private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
089    private SecureRandom secureRandom;
090    private Provider provider;
091
092    public static SSLContextBuilder create() {
093        return new SSLContextBuilder();
094    }
095
096    public SSLContextBuilder() {
097        super();
098        this.keyManagers = new LinkedHashSet<KeyManager>();
099        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}