1 /*
2  * Copyright (c) 2016. Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  * http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.http.apache.client.impl;
16
17 import com.amazonaws.SDKGlobalConfiguration;
18 import com.amazonaws.http.AmazonHttpClient;
19 import com.amazonaws.http.DelegatingDnsResolver;
20 import com.amazonaws.http.SystemPropertyTlsKeyManagersProvider;
21 import com.amazonaws.http.TlsKeyManagersProvider;
22 import com.amazonaws.http.client.ConnectionManagerFactory;
23 import com.amazonaws.http.conn.SdkPlainSocketFactory;
24 import com.amazonaws.http.conn.ssl.SdkTLSSocketFactory;
25 import com.amazonaws.http.settings.HttpClientSettings;
26 import com.amazonaws.internal.SdkSSLContext;
27 import javax.net.ssl.KeyManager;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.http.HttpHost;
31 import org.apache.http.config.ConnectionConfig;
32 import org.apache.http.config.Registry;
33 import org.apache.http.config.RegistryBuilder;
34 import org.apache.http.config.SocketConfig;
35 import org.apache.http.conn.HttpClientConnectionManager;
36 import org.apache.http.conn.socket.ConnectionSocketFactory;
37 import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
38 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
39 import org.apache.http.impl.conn.DefaultSchemePortResolver;
40 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
41 import org.apache.http.protocol.HttpContext;
42
43 import javax.net.ssl.HostnameVerifier;
44 import javax.net.ssl.SSLContext;
45 import javax.net.ssl.SSLSocket;
46 import javax.net.ssl.TrustManager;
47 import javax.net.ssl.X509TrustManager;
48 import java.io.IOException;
49 import java.net.InetSocketAddress;
50 import java.net.Socket;
51 import java.net.UnknownHostException;
52 import java.security.cert.CertificateException;
53 import java.security.cert.X509Certificate;
54 import java.util.concurrent.TimeUnit;
55
56 /**
57  * Factory class to create connection manager used by the apache client.
58  */

59 public class ApacheConnectionManagerFactory implements
60         ConnectionManagerFactory<HttpClientConnectionManager> {
61
62     private final Log LOG = LogFactory.getLog(AmazonHttpClient.class);
63
64     @Override
65     public HttpClientConnectionManager create(final HttpClientSettings settings) {
66         ConnectionSocketFactory sslsf = getPreferredSocketFactory(settings);
67
68         final PoolingHttpClientConnectionManager cm = new
69                 PoolingHttpClientConnectionManager(
70                 createSocketFactoryRegistry(sslsf),
71                 null,
72                 DefaultSchemePortResolver.INSTANCE,
73                 new DelegatingDnsResolver(settings.getDnsResolver()),
74                 settings.getConnectionPoolTTL(),
75                 TimeUnit.MILLISECONDS);
76
77         cm.setValidateAfterInactivity(settings.getValidateAfterInactivityMillis());
78         cm.setDefaultMaxPerRoute(settings.getMaxConnections());
79         cm.setMaxTotal(settings.getMaxConnections());
80         cm.setDefaultSocketConfig(buildSocketConfig(settings));
81         cm.setDefaultConnectionConfig(buildConnectionConfig(settings));
82
83         return cm;
84     }
85
86     private ConnectionSocketFactory getPreferredSocketFactory(HttpClientSettings settings) {
87         ConnectionSocketFactory sslsf = settings.getApacheHttpClientConfig().getSslSocketFactory();
88
89         return sslsf != null
90                 ? sslsf
91                 : new SdkTLSSocketFactory(
92                 SdkSSLContext.getPreferredSSLContext(getKeyManagers(settings), settings.getSecureRandom()),
93                 getHostNameVerifier(settings));
94     }
95
96     private SocketConfig buildSocketConfig(HttpClientSettings settings) {
97         return SocketConfig.custom()
98                 .setSoKeepAlive(settings.useTcpKeepAlive())
99                 .setSoTimeout(settings.getSocketTimeout())
100                 .setTcpNoDelay(true)
101                 .build();
102     }
103
104     private ConnectionConfig buildConnectionConfig(HttpClientSettings settings) {
105
106         int socketBufferSize = Math.max(settings.getSocketBufferSize()[0],
107                 settings.getSocketBufferSize()[1]);
108
109         return socketBufferSize <= 0
110                 ? null
111                 : ConnectionConfig.custom()
112                 .setBufferSize(socketBufferSize)
113                 .build();
114     }
115
116     private KeyManager[] getKeyManagers(HttpClientSettings settings) {
117         TlsKeyManagersProvider provider = settings.getTlsKeyMangersProvider();
118         if (provider == null) {
119             provider = new SystemPropertyTlsKeyManagersProvider();
120         }
121         return provider.getKeyManagers();
122     }
123
124     private HostnameVerifier getHostNameVerifier
125             (HttpClientSettings options) {
126         // TODO Need to find a better way to handle these deprecations.
127         return options.useBrowserCompatibleHostNameVerifier()
128                 ? SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
129                 : SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER;
130     }
131
132     private Registry<ConnectionSocketFactory> createSocketFactoryRegistry(ConnectionSocketFactory sslSocketFactory) {
133
134         /*
135          * If SSL cert checking for endpoints has been explicitly disabled,
136          * register a new scheme for HTTPS that won't cause self-signed certs to
137          * error out.
138          */

139         if (SDKGlobalConfiguration.isCertCheckingDisabled()) {
140             if (LOG.isWarnEnabled()) {
141                 LOG.warn("SSL Certificate checking for endpoints has been " +
142                         "explicitly disabled.");
143             }
144             sslSocketFactory = new TrustingSocketFactory();
145         }
146
147         return RegistryBuilder.<ConnectionSocketFactory>create()
148                 .register("http"new SdkPlainSocketFactory())
149                 .register("https", sslSocketFactory)
150                 .build();
151     }
152
153     /**
154      * Simple implementation of SchemeSocketFactory (and
155      * LayeredSchemeSocketFactory) that bypasses SSL certificate checks. This
156      * class is only intended to be used for testing purposes.
157      */

158     private static class TrustingSocketFactory implements
159             LayeredConnectionSocketFactory {
160
161         private SSLContext sslcontext = null;
162
163         private static SSLContext createSSLContext() throws IOException {
164             try {
165                 SSLContext context = SSLContext.getInstance("TLS");
166                 context.init(nullnew TrustManager[]{new TrustingX509TrustManager()}, null);
167                 return context;
168             } catch (Exception e) {
169                 throw new IOException(e.getMessage(), e);
170             }
171         }
172
173         @Override
174         public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException, UnknownHostException {
175             return getSSLContext().getSocketFactory().createSocket(socket,
176                     target, port, true);
177         }
178
179         @Override
180         public Socket createSocket(HttpContext context) throws IOException {
181             return getSSLContext().getSocketFactory().createSocket();
182         }
183
184         @Override
185         public Socket connectSocket(int connectTimeout, Socket sock, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
186
187             SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock :
188                     createSocket(context));
189             if (localAddress != null) sslsock.bind(localAddress);
190
191
192             sslsock.connect(remoteAddress, connectTimeout);
193             // socket timeout is set internally by the
194             // PoolingHttpClientConnectionManager.
195             return sslsock;
196         }
197
198         private SSLContext getSSLContext() throws IOException {
199             if (this.sslcontext == nullthis.sslcontext = createSSLContext();
200             return this.sslcontext;
201         }
202     }
203
204     /**
205      * Simple implementation of X509TrustManager that trusts all certificates.
206      * This class is only intended to be used for testing purposes.
207      */

208     private static class TrustingX509TrustManager implements X509TrustManager {
209         private static final X509Certificate[] X509_CERTIFICATES = new X509Certificate[0];
210
211         @Override
212         public X509Certificate[] getAcceptedIssuers() {
213             return X509_CERTIFICATES;
214         }
215
216         @Override
217         public void checkServerTrusted(X509Certificate[] chain, String authType)
218                 throws CertificateException {
219             // No-op, to trust all certs
220         }
221
222         @Override
223         public void checkClientTrusted(X509Certificate[] chain, String authType)
224                 throws CertificateException {
225             // No-op, to trust all certs
226         }
227     }
228 }
229