/* * Copyright 2014 Federal Chancellery Austria MOA-ID has been developed in a cooperation between * BRZ, the Federal Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * This product combines work with different licenses. See the "NOTICE" text file for details on the * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative * works that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egiz.eaaf.core.impl.http; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.UnrecoverableKeyException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.conn.ssl.TrustAllStrategy; import org.apache.http.entity.ContentType; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.data.Triple; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @Slf4j public class HttpUtils { private static final String ERROR_03 = "internal.httpclient.03"; /** * Simple Http response-handler that only give http status-code as result. * * @return Status-Code of http response */ public static ResponseHandler simpleStatusCodeResponseHandler() { return new ResponseHandler() { @Override public StatusLine handleResponse(HttpResponse response) throws ClientProtocolException, IOException { EntityUtils.consumeQuietly(response.getEntity()); return response.getStatusLine(); } }; } /** * Http response-handler that gives a pair of http status-code, * a copy of the full http-body as {@link InputStream} and the response {@link ContentType}. * * @return {@link Triple} of http response {@link StatusLine}, http body as {@link InputStream}, * and {@link ContentType} */ public static ResponseHandler> bodyStatusCodeResponseHandler() { return new ResponseHandler>() { @Override public Triple handleResponse(HttpResponse response) throws ClientProtocolException, IOException { byte[] bodyBytes = EntityUtils.toByteArray(response.getEntity()); return Triple.newInstance(response.getStatusLine(), new ByteArrayInputStream(bodyBytes), ContentType.getOrDefault(response.getEntity())); } }; } /** * Helper method to retrieve server URL including context path. * * @param request HttpServletRequest * @return Server URL including context path (e.g. * http://localhost:8443/moa-id-auth */ public static String getBaseUrl(final HttpServletRequest request) { final StringBuffer buffer = new StringBuffer(getServerUrl(request)); // add context path if available final String contextPath = request.getContextPath(); if (!StringUtils.isEmpty(contextPath)) { buffer.append(contextPath); } return buffer.toString(); } /** * Helper method to retrieve server URL. * * @param request HttpServletRequest * @return Server URL (e.g. http://localhost:8443) */ public static String getServerUrl(final HttpServletRequest request) { final StringBuffer buffer = new StringBuffer(); // get protocol final String protocol = request.getScheme(); buffer.append(protocol).append("://"); // server name buffer.append(request.getServerName()); // add port if necessary final int port = request.getServerPort(); if (protocol.equals("http") && port != 80 || protocol.equals("https") && port != 443) { buffer.append(':'); buffer.append(port); } return buffer.toString(); } /** * Extract the IDP PublicURLPrefix from authrequest. * * @param req HttpServletRequest * @return PublicURLPrefix which ends always without / */ public static String extractAuthUrlFromRequest(final HttpServletRequest req) { String authUrl = req.getScheme() + "://" + req.getServerName(); if (req.getScheme().equalsIgnoreCase("https") && req.getServerPort() != 443 || req.getScheme().equalsIgnoreCase("http") && req.getServerPort() != 80) { authUrl = authUrl.concat(":" + req.getServerPort()); } authUrl = authUrl.concat(req.getContextPath()); return authUrl; } /** * Extract the IDP requested URL from authrequest. * * @param req HttpServletRequest * @return RequestURL which ends always without / */ public static String extractAuthServletPathFromRequest(final HttpServletRequest req) { return extractAuthUrlFromRequest(req).concat(req.getServletPath()); } /** * Add a http GET parameter to URL. * * @param url URL * @param paramname Name of the parameter. * @param paramvalue Value of the parameter. * @return Url with parameter */ public static String addUrlParameter(final String url, final String paramname, final String paramvalue) { final String param = paramname + "=" + paramvalue; if (url.indexOf("?") < 0) { return url + "?" + param; } else { return url + "&" + param; } } /** * Inject HTTP header into http request. * *

The header is only set if HeaderValue is not null

* * @param req Http request object * @param headerName HeaderName * @param headerValue HeaderValue */ public static void addHeaderIfNotEmpty(@NonNull HttpRequest req, @NonNull String headerName, @Nullable String headerValue) { if (StringUtils.isNotEmpty(headerValue)) { req.addHeader(headerName, headerValue); } } /** * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client * authentication. * * @param keyStore KeyStore with private keys that should be * used * @param keyAlias Alias of the key that should be used. If * the alias is null, than the first key that * is found will be selected. * @param keyPasswordString Password of the Key in this keystore * @param trustAllServerCertificates Deactivate SSL server-certificate * validation * @param friendlyName FriendlyName of the http client for logging * purposes * @return {@link SSLContext} with X509 client authentication * @throws EaafConfigurationException In case of a configuration error * @throws EaafFactoryException In case of a {@link SSLContext} * initialization error */ public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair keyStore, @Nullable String keyAlias, @Nullable String keyPasswordString, boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { try { EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName); injectTrustStore(sslContextBuilder, null, trustAllServerCertificates, friendlyName); return sslContextBuilder.build(); } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException e) { throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); } } /** * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client * authentication and a custom TrustStore as {@link KeyStore}. * * @param keyStore KeyStore with private keys that should be * used * @param keyAlias Alias of the key that should be used. If * the alias is null, than the first key that * is found will be selected. * @param keyPasswordString Password of the Key in this keystore * @param trustStore TrustStore with trusted SSL certificates * @param trustAllServerCertificates Deactivate SSL server-certificate * validation * @param friendlyName FriendlyName of the http client for logging * purposes * @return {@link SSLContext} with X509 client authentication * @throws EaafConfigurationException In case of a configuration error * @throws EaafFactoryException In case of a {@link SSLContext} * initialization error */ public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair keyStore, @Nullable String keyAlias, @Nullable String keyPasswordString, @Nullable final Pair trustStore, boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { try { EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName); injectTrustStore(sslContextBuilder, trustStore, trustAllServerCertificates, friendlyName); return sslContextBuilder.build(); } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException e) { throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); } } private static void injectTrustStore(EaafSslContextBuilder sslContextBuilder, Pair trustStore, boolean trustAllServerCertificates, String friendlyName) throws NoSuchAlgorithmException, KeyStoreException { TrustStrategy trustStrategy = null; if (trustAllServerCertificates) { log.warn("Http-client:{} trusts ALL TLS server-certificates!", friendlyName); trustStrategy = new TrustAllStrategy(); } KeyStore trustStoreImpl = null; if (trustStore != null) { log.info("Http-client: {} uses custom TrustStore.", friendlyName); trustStoreImpl = trustStore.getFirst(); } sslContextBuilder.loadTrustMaterial(trustStoreImpl, trustStrategy); } private static void injectKeyStore(EaafSslContextBuilder sslContextBuilder, Pair keyStore, String keyAlias, String keyPasswordString, String friendlyName) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { Provider provider; if (keyStore.getSecond() != null) { provider = new BouncyCastleJsseProvider(keyStore.getSecond()); log.debug("KeyStore: {} provide special security-provider. Inject: {} into SSLContext", friendlyName, provider.getName()); sslContextBuilder.setProvider(provider); } log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() : keyPasswordString.toCharArray(); if (StringUtils.isNotEmpty(keyAlias)) { sslContextBuilder .loadKeyMaterial(keyStore.getFirst(), keyPassword, new EaafSslKeySelectionStrategy(keyAlias)); } else { sslContextBuilder.loadKeyMaterial(keyStore.getFirst(), keyPassword); } } }