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}