001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009 Sun Microsystems Inc.
015 * Portions Copyright 2010–2011 ApexIdentity Inc.
016 * Portions Copyright 2011-2014 ForgeRock AS.
017 */
018
019package org.forgerock.openig.http;
020
021import static java.lang.String.*;
022import static java.util.concurrent.TimeUnit.*;
023import static org.forgerock.openig.util.Duration.*;
024import static org.forgerock.openig.util.Json.*;
025import static org.forgerock.util.Utils.*;
026
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.IOException;
030import java.io.InputStream;
031import java.security.GeneralSecurityException;
032import java.security.KeyStore;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.List;
036
037import javax.net.ssl.KeyManager;
038import javax.net.ssl.KeyManagerFactory;
039import javax.net.ssl.SSLContext;
040import javax.net.ssl.TrustManager;
041import javax.net.ssl.TrustManagerFactory;
042
043import org.apache.http.Header;
044import org.apache.http.HeaderIterator;
045import org.apache.http.HttpEntity;
046import org.apache.http.HttpResponse;
047import org.apache.http.HttpVersion;
048import org.apache.http.StatusLine;
049import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
050import org.apache.http.client.methods.HttpRequestBase;
051import org.apache.http.client.params.HttpClientParams;
052import org.apache.http.client.protocol.RequestAddCookies;
053import org.apache.http.client.protocol.RequestProxyAuthentication;
054import org.apache.http.client.protocol.RequestTargetAuthentication;
055import org.apache.http.client.protocol.ResponseProcessCookies;
056import org.apache.http.conn.ClientConnectionManager;
057import org.apache.http.conn.params.ConnManagerParams;
058import org.apache.http.conn.params.ConnPerRouteBean;
059import org.apache.http.conn.scheme.PlainSocketFactory;
060import org.apache.http.conn.scheme.Scheme;
061import org.apache.http.conn.scheme.SchemeRegistry;
062import org.apache.http.conn.ssl.SSLSocketFactory;
063import org.apache.http.conn.ssl.X509HostnameVerifier;
064import org.apache.http.entity.InputStreamEntity;
065import org.apache.http.impl.NoConnectionReuseStrategy;
066import org.apache.http.impl.client.DefaultHttpClient;
067import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
068import org.apache.http.params.BasicHttpParams;
069import org.apache.http.params.HttpConnectionParams;
070import org.apache.http.params.HttpProtocolParams;
071import org.forgerock.json.fluent.JsonValue;
072import org.forgerock.openig.header.ConnectionHeader;
073import org.forgerock.openig.header.ContentEncodingHeader;
074import org.forgerock.openig.header.ContentLengthHeader;
075import org.forgerock.openig.header.ContentTypeHeader;
076import org.forgerock.openig.heap.GenericHeaplet;
077import org.forgerock.openig.heap.HeapException;
078import org.forgerock.openig.io.BranchingStreamWrapper;
079import org.forgerock.openig.io.TemporaryStorage;
080import org.forgerock.openig.log.Logger;
081import org.forgerock.openig.util.CaseInsensitiveSet;
082import org.forgerock.openig.util.Duration;
083import org.forgerock.openig.util.NoRetryHttpRequestRetryHandler;
084
085/**
086 * Submits requests to remote servers. In this implementation, requests are
087 * dispatched through the <a href="http://hc.apache.org/">Apache
088 * HttpComponents</a> client.
089 * <p>
090 * <pre>
091 *   {
092 *     "name": "HttpClient",
093 *     "type": "HttpClient",
094 *     "config": {
095 *       "connections": 64,
096 *       "disableReuseConnection": true,
097 *       "disableRetries": true,
098 *       "hostnameVerifier": "ALLOW_ALL",
099 *       "soTimeout": "10 seconds",
100 *       "connectionTimeout": "10 seconds",
101 *       "keystore": {
102 *           "file": "/path/to/keystore.jks",
103 *           "password": "changeit"
104 *       },
105 *       "truststore": {
106 *           "file": "/path/to/keystore.jks",
107 *           "password": "changeit"
108 *       },
109 *       "keyManager": [ "RefToKeyManager", ... ]
110 *       "trustManager": [ "RefToTrustManager", ... ]
111 *     }
112 *   }
113 * </pre>
114 * <p>
115 * <strong>Note:</strong> This implementation does not verify hostnames for
116 * outgoing SSL connections by default. This is because the gateway will usually access the
117 * SSL endpoint using a raw IP address rather than a fully-qualified hostname.
118 * <p>
119 * It's possible to override that behavior using the {@literal hostnameVerifier} attribute (case is not important,
120 * but unknown values will produce an error).
121 * <p>
122 * Accepted values are:
123 * <ul>
124 *     <li>{@literal ALLOW_ALL} (the default)</li>
125 *     <li>{@literal BROWSER_COMPATIBLE}</li>
126 *     <li>{@literal STRICT}</li>
127 * </ul>
128 * <p>
129 * The <strong>deprecated</strong> {@literal keystore} and {@literal truststore} optional attributes are both
130 * supporting the following attributes:
131 * <ul>
132 *     <li>{@literal file}: path to the key store</li>
133 *     <li>{@literal type}: key store type (defaults to {@literal JKS})</li>
134 *     <li>{@literal alg}: certificate algorithm to use (defaults to {@literal SunX509})</li>
135 *     <li>{@literal password}: mandatory for key store, optional for trust store, defined as an
136 *     {@link org.forgerock.openig.el.Expression}</li>
137 * </ul>
138 * <p>
139 * The new (since OpenIG 3.1) {@literal keyManager} and {@literal trustManager} optional attributes are referencing a
140 * list of {@link KeyManager} (and {@link TrustManager} respectively). They support singleton value (use a single
141 * reference) as well as multi-valued references (a list):
142 * <pre>
143 *     "keyManager": "SingleKeyManagerReference",
144 *     "trustManager": [ "RefOne", "RefTwo" ]
145 * </pre>
146 * <p>
147 * The {@literal soTimeout} optional attribute specifies a socket timeout (the given amount of time a connection
148 * will live before being considered a stalled and automatically destroyed). It defaults to {@literal 10 seconds}.
149 * <p>
150 * The {@literal connectionTimeout} optional attribute specifies a connection timeout (the given amount of time to
151 * wait until the connection is established). It defaults to {@literal 10 seconds}.
152 *
153 * @see Duration
154 * @see org.forgerock.openig.security.KeyManagerHeaplet
155 * @see org.forgerock.openig.security.TrustManagerHeaplet
156 */
157public class HttpClient {
158
159    /**
160     * Key to retrieve an {@link HttpClient} instance from the {@link org.forgerock.openig.heap.Heap}.
161     */
162    public static final String HTTP_CLIENT_HEAP_KEY = "HttpClient";
163
164    /** Reuse of Http connection is disabled by default. */
165    public static final boolean DISABLE_CONNECTION_REUSE = false;
166
167    /** Http connection retries are disabled by default. */
168    public static final boolean DISABLE_RETRIES = false;
169
170    /** Default maximum number of collections through HTTP client. */
171    public static final int DEFAULT_CONNECTIONS = 64;
172
173    /**
174     * Value of the default timeout.
175     */
176    public static final String TEN_SECONDS = "10 seconds";
177
178    /**
179     * Default socket timeout as a {@link Duration}.
180     */
181    public static final Duration DEFAULT_SO_TIMEOUT = duration(TEN_SECONDS);
182
183    /**
184     * Default connection timeout as a {@link Duration}.
185     */
186    public static final Duration DEFAULT_CONNECTION_TIMEOUT = duration(TEN_SECONDS);
187
188    /** A request that encloses an entity. */
189    private static class EntityRequest extends HttpEntityEnclosingRequestBase {
190        private final String method;
191
192        public EntityRequest(final Request request) {
193            this.method = request.getMethod();
194            final InputStreamEntity entity =
195                    new InputStreamEntity(request.getEntity().getRawInputStream(),
196                            new ContentLengthHeader(request).getLength());
197            entity.setContentType(new ContentTypeHeader(request).toString());
198            entity.setContentEncoding(new ContentEncodingHeader(request).toString());
199            setEntity(entity);
200        }
201
202        @Override
203        public String getMethod() {
204            return method;
205        }
206    }
207
208    /** A request that does not enclose an entity. */
209    private static class NonEntityRequest extends HttpRequestBase {
210        private final String method;
211
212        public NonEntityRequest(final Request request) {
213            this.method = request.getMethod();
214            final Header[] contentLengthHeader = getHeaders(ContentLengthHeader.NAME);
215            if ((contentLengthHeader == null || contentLengthHeader.length == 0)
216                    && ("PUT".equals(method) || "POST".equals(method) || "PROPFIND".equals(method))) {
217                setHeader(ContentLengthHeader.NAME, "0");
218            }
219        }
220
221        @Override
222        public String getMethod() {
223            return method;
224        }
225    }
226
227    /**
228     * The set of accepted configuration values for the {@literal hostnameVerifier} attribute.
229     */
230    private static enum Verifier {
231        ALLOW_ALL {
232            @Override
233            X509HostnameVerifier getHostnameVerifier() {
234                return SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
235            }
236        },
237        BROWSER_COMPATIBLE {
238            @Override
239            X509HostnameVerifier getHostnameVerifier() {
240                return SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
241            }
242        },
243        STRICT {
244            @Override
245            X509HostnameVerifier getHostnameVerifier() {
246                return SSLSocketFactory.STRICT_HOSTNAME_VERIFIER;
247            }
248        };
249
250        abstract X509HostnameVerifier getHostnameVerifier();
251    }
252
253    /** Headers that are suppressed in request. */
254    // FIXME: How should the the "Expect" header be handled?
255    private static final CaseInsensitiveSet SUPPRESS_REQUEST_HEADERS = new CaseInsensitiveSet(
256            Arrays.asList(
257                    // populated in outgoing request by EntityRequest (HttpEntityEnclosingRequestBase):
258                    "Content-Encoding", "Content-Length", "Content-Type",
259                    // hop-to-hop headers, not forwarded by proxies, per RFC 2616 §13.5.1:
260                    "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE",
261                    "Trailers", "Transfer-Encoding", "Upgrade")
262    );
263
264    /** Headers that are suppressed in response. */
265    private static final CaseInsensitiveSet SUPPRESS_RESPONSE_HEADERS = new CaseInsensitiveSet(
266            Arrays.asList(
267                    // hop-to-hop headers, not forwarded by proxies, per RFC 2616 §13.5.1:
268                    "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE",
269                    "Trailers", "Transfer-Encoding", "Upgrade")
270    );
271
272    /**
273     * Returns a new SSL socket factory that does not perform hostname verification.
274     *
275     * @param keyManagers
276     *         Provides Keys/Certificates in case of SSL/TLS connections
277     * @param trustManagers
278     *         Provides TrustManagers in case of SSL/TLS connections
279     * @param hostnameVerifier hostname verification strategy
280     * @throws GeneralSecurityException
281     *         if the SSL algorithm is unsupported or if an error occurs during SSL configuration
282     */
283    private static SSLSocketFactory newSSLSocketFactory(final KeyManager[] keyManagers,
284                                                        final TrustManager[] trustManagers,
285                                                        final X509HostnameVerifier hostnameVerifier)
286            throws GeneralSecurityException {
287        SSLContext context = SSLContext.getInstance("TLS");
288        context.init(keyManagers, trustManagers, null);
289        SSLSocketFactory factory = new SSLSocketFactory(context);
290        factory.setHostnameVerifier(hostnameVerifier);
291        return factory;
292    }
293
294    /** The HTTP client to transmit requests through. */
295    private final DefaultHttpClient httpClient;
296    /**
297     * Allocates temporary buffers for caching streamed content during request
298     * processing.
299     */
300    private final TemporaryStorage storage;
301
302    /**
303     * Creates a new client handler which will cache at most 64 connections, allow all host names for SSL requests
304     * and has a both a default connection and so timeout.
305     *
306     * @param storage the TemporaryStorage to use
307     * @throws GeneralSecurityException
308     *         if the SSL algorithm is unsupported or if an error occurs during SSL configuration
309     */
310    public HttpClient(final TemporaryStorage storage) throws GeneralSecurityException {
311        this(storage,
312             DEFAULT_CONNECTIONS,
313             null,
314             null,
315             Verifier.ALLOW_ALL,
316             DEFAULT_SO_TIMEOUT,
317             DEFAULT_CONNECTION_TIMEOUT);
318    }
319
320    /**
321     * Creates a new client handler with the specified maximum number of cached connections.
322     *
323     * @param storage the {@link TemporaryStorage} to use
324     * @param connections the maximum number of connections to open.
325     * @param keyManagers Provides Keys/Certificates in case of SSL/TLS connections
326     * @param trustManagers Provides TrustManagers in case of SSL/TLS connections
327     * @param verifier hostname verification strategy
328     * @param soTimeout socket timeout duration
329     * @param connectionTimeout connection timeout duration
330     * @throws GeneralSecurityException
331     *         if the SSL algorithm is unsupported or if an error occurs during SSL configuration
332     */
333    public HttpClient(final TemporaryStorage storage,
334                      final int connections,
335                      final KeyManager[] keyManagers,
336                      final TrustManager[] trustManagers,
337                      final Verifier verifier,
338                      final Duration soTimeout,
339                      final Duration connectionTimeout) throws GeneralSecurityException {
340        this.storage = storage;
341
342        final BasicHttpParams parameters = new BasicHttpParams();
343        final int maxConnections = connections <= 0 ? DEFAULT_CONNECTIONS : connections;
344        ConnManagerParams.setMaxTotalConnections(parameters, maxConnections);
345        ConnManagerParams.setMaxConnectionsPerRoute(parameters, new ConnPerRouteBean(maxConnections));
346        if (!soTimeout.isUnlimited()) {
347            HttpConnectionParams.setSoTimeout(parameters, (int) soTimeout.to(MILLISECONDS));
348        }
349        if (!connectionTimeout.isUnlimited()) {
350            HttpConnectionParams.setConnectionTimeout(parameters, (int) connectionTimeout.to(MILLISECONDS));
351        }
352        HttpProtocolParams.setVersion(parameters, HttpVersion.HTTP_1_1);
353        HttpClientParams.setRedirecting(parameters, false);
354
355        final SchemeRegistry registry = new SchemeRegistry();
356        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
357        registry.register(new Scheme("https",
358                                     newSSLSocketFactory(keyManagers,
359                                                         trustManagers,
360                                                         verifier.getHostnameVerifier()),
361                                     443));
362        final ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(parameters, registry);
363
364        httpClient = new DefaultHttpClient(connectionManager, parameters);
365        httpClient.removeRequestInterceptorByClass(RequestAddCookies.class);
366        httpClient.removeRequestInterceptorByClass(RequestProxyAuthentication.class);
367        httpClient.removeRequestInterceptorByClass(RequestTargetAuthentication.class);
368        httpClient.removeResponseInterceptorByClass(ResponseProcessCookies.class);
369    }
370
371    /**
372     * Disables connection caching.
373     *
374     * @return this HTTP client.
375     */
376    public HttpClient disableConnectionReuse() {
377        httpClient.setReuseStrategy(new NoConnectionReuseStrategy());
378        return this;
379    }
380
381    /**
382     * Disables automatic retrying of failed requests.
383     *
384     * @param logger a logger which should be used for logging the reason that a
385     * request failed.
386     * @return this HTTP client.
387     */
388    public HttpClient disableRetries(final Logger logger) {
389        httpClient.setHttpRequestRetryHandler(new NoRetryHttpRequestRetryHandler(logger));
390        return this;
391    }
392
393    /**
394     * Submits the exchange request to the remote server. Creates and populates
395     * the exchange response from that provided by the remote server.
396     *
397     * @param exchange The HTTP exchange containing the request to send and where the
398     * response will be placed.
399     * @throws IOException If an IO error occurred while performing the request.
400     */
401    public void execute(final Exchange exchange) throws IOException {
402        // recover any previous response connection, if present
403        closeSilently(exchange.response);
404        exchange.response = execute(exchange.request);
405    }
406
407    /**
408     * Submits the request to the remote server. Creates and populates the
409     * response from that provided by the remote server.
410     *
411     * @param request The HTTP request to send.
412     * @return The HTTP response.
413     * @throws IOException If an IO error occurred while performing the request.
414     */
415    public Response execute(final Request request) throws IOException {
416        final HttpRequestBase clientRequest =
417                request.getEntity().isEmpty() ? new NonEntityRequest(request) : new EntityRequest(request);
418        clientRequest.setURI(request.getUri().asURI());
419        // connection headers to suppress
420        final CaseInsensitiveSet suppressConnection = new CaseInsensitiveSet();
421        // parse request connection headers to be suppressed in request
422        suppressConnection.addAll(new ConnectionHeader(request).getTokens());
423        // request headers
424        for (final String name : request.getHeaders().keySet()) {
425            if (!SUPPRESS_REQUEST_HEADERS.contains(name) && !suppressConnection.contains(name)) {
426                for (final String value : request.getHeaders().get(name)) {
427                    clientRequest.addHeader(name, value);
428                }
429            }
430        }
431        // send request
432        final HttpResponse clientResponse = httpClient.execute(clientRequest);
433        final Response response = new Response();
434        // response entity
435        final HttpEntity clientResponseEntity = clientResponse.getEntity();
436        if (clientResponseEntity != null) {
437            response.setEntity(new BranchingStreamWrapper(clientResponseEntity.getContent(),
438                    storage));
439        }
440        // response status line
441        final StatusLine statusLine = clientResponse.getStatusLine();
442        response.setVersion(statusLine.getProtocolVersion().toString());
443        response.setStatus(statusLine.getStatusCode());
444        response.setReason(statusLine.getReasonPhrase());
445        // parse response connection headers to be suppressed in response
446        suppressConnection.clear();
447        suppressConnection.addAll(new ConnectionHeader(response).getTokens());
448        // response headers
449        for (final HeaderIterator i = clientResponse.headerIterator(); i.hasNext();) {
450            final Header header = i.nextHeader();
451            final String name = header.getName();
452            if (!SUPPRESS_RESPONSE_HEADERS.contains(name) && !suppressConnection.contains(name)) {
453                response.getHeaders().add(name, header.getValue());
454            }
455        }
456        // TODO: decide if need to try-finally to call httpRequest.abort?
457        return response;
458    }
459
460    /**
461     * Creates and initializes a http client object in a heap environment.
462     */
463    public static class Heaplet extends GenericHeaplet {
464
465        @Override
466        public Object create() throws HeapException {
467            // optional, default to DEFAULT_CONNECTIONS number of connections
468            Integer connections = config.get("connections").defaultTo(DEFAULT_CONNECTIONS).asInteger();
469            // determines if connections should be reused, disables keep-alive
470            Boolean disableReuseConnection = config.get("disableReuseConnection")
471                                                   .defaultTo(DISABLE_CONNECTION_REUSE)
472                                                   .asBoolean();
473            // determines if requests should be retried on failure
474            Boolean disableRetries = config.get("disableRetries").defaultTo(DISABLE_RETRIES).asBoolean();
475
476            Verifier verifier = config.get("hostnameVerifier")
477                                      .defaultTo(Verifier.ALLOW_ALL.name())
478                                      .asEnum(Verifier.class);
479
480            // Timeouts
481            Duration soTimeout = duration(config.get("soTimeout").defaultTo(TEN_SECONDS).asString());
482            Duration connectionTimeout = duration(config.get("connectionTimeout")
483                                                        .defaultTo(TEN_SECONDS)
484                                                        .asString());
485
486            // Create the HttpClient instance
487            try {
488                HttpClient client = new HttpClient(storage,
489                                                   connections,
490                                                   getKeyManagers(),
491                                                   getTrustManagers(),
492                                                   verifier,
493                                                   soTimeout,
494                                                   connectionTimeout);
495
496                if (disableRetries) {
497                    client.disableRetries(logger);
498                }
499                if (disableReuseConnection) {
500                    client.disableConnectionReuse();
501                }
502
503                return client;
504            } catch (GeneralSecurityException e) {
505                throw new HeapException(format("Cannot build HttpClient named '%s'", name), e);
506            }
507        }
508
509        private TrustManager[] getTrustManagers() throws HeapException {
510            // Build an optional TrustManagerFactory
511            TrustManager[] trustManagers = null;
512            if (config.isDefined("truststore")) {
513                // This attribute is deprecated: warn the user
514                warnForDeprecation(config, logger, "trustManager", "truststore");
515
516                JsonValue store = config.get("truststore");
517                File truststoreFile = store.get("file").required().asFile();
518
519                // Password is optional for trust store
520                String password = evaluate(store.get("password"));
521                String type = store.get("type").defaultTo("JKS").asString().toUpperCase();
522                String algorithm = store.get("alg").defaultTo("SunX509").asString();
523
524                trustManagers = buildTrustManagerFactory(truststoreFile, type, algorithm, password).getTrustManagers();
525            }
526
527            // Uses TrustManager references
528            if (config.isDefined("trustManager")) {
529                if (trustManagers != null) {
530                    logger.warning("Cannot use both 'truststore' and 'trustManager' attributes, "
531                                   + "will use configuration from 'trustManager' attribute");
532                }
533                JsonValue trustManagerConfig = config.get("trustManager");
534                List<TrustManager> managers = new ArrayList<TrustManager>();
535                if (trustManagerConfig.isList()) {
536                    managers.addAll(trustManagerConfig.asList(ofRequiredHeapObject(heap, TrustManager.class)));
537                } else {
538                    managers.add(heap.resolve(trustManagerConfig, TrustManager.class));
539                }
540                trustManagers = managers.toArray(new TrustManager[managers.size()]);
541            }
542            return trustManagers;
543        }
544
545        private KeyManager[] getKeyManagers() throws HeapException {
546            // Build an optional KeyManagerFactory
547            KeyManager[] keyManagers = null;
548            if (config.isDefined("keystore")) {
549                // This attribute is deprecated: warn the user
550                warnForDeprecation(config, logger, "keyManager", "keystore");
551
552                JsonValue store = config.get("keystore");
553                File keystoreFile = store.get("file").required().asFile();
554                String password = evaluate(store.get("password").required());
555                String type = store.get("type").defaultTo("JKS").asString().toUpperCase();
556                String algorithm = store.get("alg").defaultTo("SunX509").asString();
557
558                keyManagers = buildKeyManagerFactory(keystoreFile, type, algorithm, password).getKeyManagers();
559            }
560
561            // Uses KeyManager references
562            if (config.isDefined("keyManager")) {
563                if (keyManagers != null) {
564                    logger.warning("Cannot use both 'keystore' and 'keyManager' attributes, "
565                                           + "will use configuration from 'keyManager' attribute");
566                }
567                JsonValue keyManagerConfig = config.get("keyManager");
568                List<KeyManager> managers = new ArrayList<KeyManager>();
569                if (keyManagerConfig.isList()) {
570                    managers.addAll(keyManagerConfig.asList(ofRequiredHeapObject(heap, KeyManager.class)));
571                } else {
572                    managers.add(heap.resolve(keyManagerConfig, KeyManager.class));
573                }
574                keyManagers = managers.toArray(new KeyManager[managers.size()]);
575            }
576            return keyManagers;
577        }
578
579        private TrustManagerFactory buildTrustManagerFactory(final File truststoreFile,
580                                                             final String type,
581                                                             final String algorithm,
582                                                             final String password)
583                throws HeapException {
584            try {
585                TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);
586                KeyStore store = buildKeyStore(truststoreFile, type, password);
587                factory.init(store);
588                return factory;
589            } catch (Exception e) {
590                throw new HeapException(format(
591                        "Cannot build TrustManagerFactory[alg:%s] from KeyStore[type:%s] stored in %s",
592                        algorithm,
593                        type,
594                        truststoreFile),
595                                        e);
596            }
597        }
598
599        private KeyManagerFactory buildKeyManagerFactory(final File keystoreFile,
600                                                         final String type,
601                                                         final String algorithm,
602                                                         final String password)
603                throws HeapException {
604            try {
605                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
606                KeyStore keyStore = buildKeyStore(keystoreFile, type, password);
607                keyManagerFactory.init(keyStore, password.toCharArray());
608                return keyManagerFactory;
609            } catch (Exception e) {
610                throw new HeapException(format(
611                        "Cannot build KeyManagerFactory[alg:%s] from KeyStore[type:%s] stored in %s",
612                        algorithm,
613                        type,
614                        keystoreFile),
615                                        e);
616            }
617        }
618
619        private KeyStore buildKeyStore(final File keystoreFile, final String type, final String password)
620                throws Exception {
621            KeyStore keyStore = KeyStore.getInstance(type);
622            InputStream keyInput = null;
623            try {
624                keyInput = new FileInputStream(keystoreFile);
625                char[] credentials = (password == null) ? null : password.toCharArray();
626                keyStore.load(keyInput, credentials);
627            } finally {
628                closeSilently(keyInput);
629            }
630            return keyStore;
631        }
632    }
633
634}