Java tutorial
/** * The MIT License * Copyright (c) 2016 Estonian Information System Authority (RIA), Population Register Centre (VRK) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package ee.ria.xroad.common.opmonitoring; import java.io.FileInputStream; import java.io.InputStream; import java.net.Socket; import java.security.Principal; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509TrustManager; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import ee.ria.xroad.common.conf.InternalSSLKey; import ee.ria.xroad.common.util.CryptoUtils; /** * Operational monitoring daemon HTTP client. */ @Slf4j public final class OpMonitoringDaemonHttpClient { // HttpClient configuration parameters. private static final int DEFAULT_CLIENT_MAX_TOTAL_CONNECTIONS = 10000; private static final int DEFAULT_CLIENT_MAX_CONNECTIONS_PER_ROUTE = 10000; private OpMonitoringDaemonHttpClient() { } /** * Creates HTTP client. * @param authKey the client's authentication key * @param connectionTimeoutMilliseconds connection timeout in milliseconds * @param socketTimeoutMilliseconds socket timeout in milliseconds * @return HTTP client * @throws Exception if creating a HTTPS client and SSLContext initialization fails */ public static CloseableHttpClient createHttpClient(InternalSSLKey authKey, int connectionTimeoutMilliseconds, int socketTimeoutMilliseconds) throws Exception { return createHttpClient(authKey, DEFAULT_CLIENT_MAX_TOTAL_CONNECTIONS, DEFAULT_CLIENT_MAX_CONNECTIONS_PER_ROUTE, connectionTimeoutMilliseconds, socketTimeoutMilliseconds); } /** * Creates HTTP client. * @param authKey the client's authentication key * @param clientMaxTotalConnections client max total connections * @param clientMaxConnectionsPerRoute client max connections per route * @param connectionTimeoutMilliseconds connection timeout in milliseconds * @param socketTimeoutMilliseconds socket timeout in milliseconds * @return HTTP client * @throws Exception if creating a HTTPS client and SSLContext * initialization fails */ public static CloseableHttpClient createHttpClient(InternalSSLKey authKey, int clientMaxTotalConnections, int clientMaxConnectionsPerRoute, int connectionTimeoutMilliseconds, int socketTimeoutMilliseconds) throws Exception { log.trace("createHttpClient()"); RegistryBuilder<ConnectionSocketFactory> sfr = RegistryBuilder.<ConnectionSocketFactory>create(); if ("https".equalsIgnoreCase(OpMonitoringSystemProperties.getOpMonitorDaemonScheme())) { sfr.register("https", createSSLSocketFactory(authKey)); } else { sfr.register("http", PlainConnectionSocketFactory.INSTANCE); } PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(sfr.build()); cm.setMaxTotal(clientMaxTotalConnections); cm.setDefaultMaxPerRoute(clientMaxConnectionsPerRoute); cm.setDefaultSocketConfig(SocketConfig.custom().setTcpNoDelay(true).build()); RequestConfig.Builder rb = RequestConfig.custom().setConnectTimeout(connectionTimeoutMilliseconds) .setConnectionRequestTimeout(connectionTimeoutMilliseconds) .setSocketTimeout(socketTimeoutMilliseconds); HttpClientBuilder cb = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(rb.build()); // Disable request retry cb.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)); return cb.build(); } private static SSLConnectionSocketFactory createSSLSocketFactory(InternalSSLKey authKey) throws Exception { SSLContext ctx = SSLContext.getInstance(CryptoUtils.SSL_PROTOCOL); ctx.init(getKeyManager(authKey), new TrustManager[] { new OpMonitorTrustManager() }, new SecureRandom()); return new SSLConnectionSocketFactory(ctx.getSocketFactory(), new String[] { CryptoUtils.SSL_PROTOCOL }, CryptoUtils.getINCLUDED_CIPHER_SUITES(), NoopHostnameVerifier.INSTANCE); // We don't need hostname verification } private static KeyManager[] getKeyManager(InternalSSLKey authKey) { if (authKey == null) { log.error("No internal TLS key required by operational monitoring daemon HTTP client"); return null; } return new KeyManager[] { new OpMonitorClientKeyManager(authKey) }; } private static final class OpMonitorTrustManager implements X509TrustManager { private X509Certificate opMonitorCert = null; private OpMonitorTrustManager() { String monitorCertPath = OpMonitoringSystemProperties.getOpMonitorCertificatePath(); try (InputStream monitorCertStream = new FileInputStream(monitorCertPath)) { opMonitorCert = CryptoUtils.readCertificate(monitorCertStream); } catch (Exception e) { log.error("Could not load operational monitoring daemon certificate '{}'", monitorCertPath, e); } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain.length == 0) { throw new CertificateException("Server did not send TLS certificate"); } log.trace("Received server certificate {}", chain[0]); if (opMonitorCert == null) { throw new CertificateException( "Operational monitoring daemon certificate not loaded, cannot verify server"); } if (!chain[0].equals(opMonitorCert)) { throw new CertificateException( "Server TLS certificate does not match expected operational monitoring daemon certificate"); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } @RequiredArgsConstructor private static final class OpMonitorClientKeyManager extends X509ExtendedKeyManager { private static final String ALIAS = "OpMonitorClientKeyManager"; private final InternalSSLKey authKey; @Override public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { return ALIAS; } @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { return ALIAS; } @Override public X509Certificate[] getCertificateChain(String alias) { return new X509Certificate[] { authKey.getCert() }; } @Override public String[] getClientAliases(String keyType, Principal[] issuers) { return null; } @Override public PrivateKey getPrivateKey(String alias) { return authKey.getKey(); } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { return null; } @Override public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { return ALIAS; } @Override public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { return ALIAS; } } }