View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.ssl;
29  
30  import java.io.IOException;
31  import java.net.Socket;
32  import java.net.SocketAddress;
33  import java.security.cert.Certificate;
34  import java.security.cert.X509Certificate;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collection;
38  import java.util.List;
39  import java.util.Objects;
40  
41  import javax.net.ssl.HostnameVerifier;
42  import javax.net.ssl.SSLContext;
43  import javax.net.ssl.SSLEngine;
44  import javax.net.ssl.SSLException;
45  import javax.net.ssl.SSLHandshakeException;
46  import javax.net.ssl.SSLParameters;
47  import javax.net.ssl.SSLPeerUnverifiedException;
48  import javax.net.ssl.SSLSession;
49  import javax.net.ssl.SSLSocket;
50  import javax.security.auth.x500.X500Principal;
51  
52  import org.apache.hc.client5.http.config.TlsConfig;
53  import org.apache.hc.core5.annotation.Contract;
54  import org.apache.hc.core5.annotation.ThreadingBehavior;
55  import org.apache.hc.core5.concurrent.FutureCallback;
56  import org.apache.hc.core5.http.HttpHost;
57  import org.apache.hc.core5.http.URIScheme;
58  import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
59  import org.apache.hc.core5.http.protocol.HttpContext;
60  import org.apache.hc.core5.http.ssl.TLS;
61  import org.apache.hc.core5.http.ssl.TlsCiphers;
62  import org.apache.hc.core5.http2.HttpVersionPolicy;
63  import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
64  import org.apache.hc.core5.http2.ssl.H2TlsSupport;
65  import org.apache.hc.core5.io.Closer;
66  import org.apache.hc.core5.net.NamedEndpoint;
67  import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
68  import org.apache.hc.core5.reactor.ssl.TlsDetails;
69  import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer;
70  import org.apache.hc.core5.util.Args;
71  import org.apache.hc.core5.util.Timeout;
72  import org.slf4j.Logger;
73  import org.slf4j.LoggerFactory;
74  
75  @Contract(threading = ThreadingBehavior.STATELESS)
76  abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrategy {
77  
78      private static final Logger LOG = LoggerFactory.getLogger(AbstractClientTlsStrategy.class);
79  
80      private final SSLContext sslContext;
81      private final String[] supportedProtocols;
82      private final String[] supportedCipherSuites;
83      private final SSLBufferMode sslBufferManagement;
84      private final HostnameVerificationPolicy hostnameVerificationPolicy;
85      private final HostnameVerifier hostnameVerifier;
86  
87      AbstractClientTlsStrategy(
88              final SSLContext sslContext,
89              final String[] supportedProtocols,
90              final String[] supportedCipherSuites,
91              final SSLBufferMode sslBufferManagement,
92              final HostnameVerificationPolicy hostnameVerificationPolicy,
93              final HostnameVerifier hostnameVerifier) {
94          super();
95          this.sslContext = Args.notNull(sslContext, "SSL context");
96          this.supportedProtocols = supportedProtocols;
97          this.supportedCipherSuites = supportedCipherSuites;
98          this.sslBufferManagement = sslBufferManagement != null ? sslBufferManagement : SSLBufferMode.STATIC;
99          this.hostnameVerificationPolicy = hostnameVerificationPolicy != null ? hostnameVerificationPolicy : HostnameVerificationPolicy.BOTH;
100         this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier :
101                 (this.hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN ? NoopHostnameVerifier.INSTANCE : HttpsSupport.getDefaultHostnameVerifier());
102     }
103 
104     /**
105      * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)}
106      */
107     @Deprecated
108     @Override
109     public boolean upgrade(
110             final TransportSecurityLayer tlsSession,
111             final HttpHost host,
112             final SocketAddress localAddress,
113             final SocketAddress remoteAddress,
114             final Object attachment,
115             final Timeout handshakeTimeout) {
116         upgrade(tlsSession, host, attachment, handshakeTimeout, null);
117         return true;
118     }
119 
120     @Override
121     public void upgrade(
122             final TransportSecurityLayer tlsSession,
123             final NamedEndpoint endpoint,
124             final Object attachment,
125             final Timeout handshakeTimeout,
126             final FutureCallback<TransportSecurityLayer> callback) {
127         tlsSession.startTls(sslContext, endpoint, sslBufferManagement, (e, sslEngine) -> {
128 
129             final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
130             final HttpVersionPolicy versionPolicy = tlsConfig.getHttpVersionPolicy();
131 
132             final SSLParameters sslParameters = sslEngine.getSSLParameters();
133             final String[] supportedProtocols = tlsConfig.getSupportedProtocols();
134             if (supportedProtocols != null) {
135                 sslParameters.setProtocols(supportedProtocols);
136             } else if (this.supportedProtocols != null) {
137                 sslParameters.setProtocols(this.supportedProtocols);
138             } else if (versionPolicy != HttpVersionPolicy.FORCE_HTTP_1) {
139                 sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols()));
140             }
141             final String[] supportedCipherSuites = tlsConfig.getSupportedCipherSuites();
142             if (supportedCipherSuites != null) {
143                 sslParameters.setCipherSuites(supportedCipherSuites);
144             } else if (this.supportedCipherSuites != null) {
145                 sslParameters.setCipherSuites(this.supportedCipherSuites);
146             } else if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) {
147                 sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites()));
148             }
149 
150             if (versionPolicy != HttpVersionPolicy.FORCE_HTTP_1) {
151                 H2TlsSupport.setEnableRetransmissions(sslParameters, false);
152             }
153 
154             applyParameters(sslEngine, sslParameters, H2TlsSupport.selectApplicationProtocols(versionPolicy));
155 
156             if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
157                 sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
158             }
159 
160             initializeEngine(sslEngine);
161 
162             if (LOG.isDebugEnabled()) {
163                 LOG.debug("Enabled protocols: {}", Arrays.asList(sslEngine.getEnabledProtocols()));
164                 LOG.debug("Enabled cipher suites: {}", Arrays.asList(sslEngine.getEnabledCipherSuites()));
165                 LOG.debug("Starting handshake ({})", handshakeTimeout);
166             }
167         }, (e, sslEngine) -> {
168             verifySession(endpoint.getHostName(), sslEngine.getSession());
169             final TlsDetails tlsDetails = createTlsDetails(sslEngine);
170             final String negotiatedCipherSuite = sslEngine.getSession().getCipherSuite();
171             if (tlsDetails != null && ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
172                 if (TlsCiphers.isH2Blacklisted(negotiatedCipherSuite)) {
173                     throw new SSLHandshakeException("Cipher suite `" + negotiatedCipherSuite
174                         + "` does not provide adequate security for HTTP/2");
175                 }
176             }
177             return tlsDetails;
178         }, handshakeTimeout, callback);
179     }
180 
181     abstract void applyParameters(SSLEngine sslEngine, SSLParameters sslParameters, String[] appProtocols);
182 
183     abstract TlsDetails createTlsDetails(SSLEngine sslEngine);
184 
185     protected void initializeEngine(final SSLEngine sslEngine) {
186     }
187 
188     protected void initializeSocket(final SSLSocket socket) {
189     }
190 
191     protected void verifySession(
192             final String hostname,
193             final SSLSession sslsession) throws SSLException {
194         verifySession(hostname, sslsession,
195                 hostnameVerificationPolicy == HostnameVerificationPolicy.CLIENT || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH ? hostnameVerifier : null);
196     }
197 
198     @Override
199     public SSLSocket upgrade(final Socket socket,
200                              final String target,
201                              final int port,
202                              final Object attachment,
203                              final HttpContext context) throws IOException {
204         final SSLSocket upgradedSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(
205                 socket,
206                 target,
207                 port,
208                 false);
209         try {
210             executeHandshake(upgradedSocket, target, attachment);
211             return upgradedSocket;
212         } catch (IOException | RuntimeException ex) {
213             Closer.closeQuietly(upgradedSocket);
214             throw ex;
215         }
216     }
217 
218     private void executeHandshake(
219             final SSLSocket upgradedSocket,
220             final String target,
221             final Object attachment) throws IOException {
222         final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
223 
224         final SSLParameters sslParameters = upgradedSocket.getSSLParameters();
225         if (supportedProtocols != null) {
226             sslParameters.setProtocols(supportedProtocols);
227         } else {
228             sslParameters.setProtocols((TLS.excludeWeak(upgradedSocket.getEnabledProtocols())));
229         }
230         if (supportedCipherSuites != null) {
231             sslParameters.setCipherSuites(supportedCipherSuites);
232         } else {
233             sslParameters.setCipherSuites(TlsCiphers.excludeWeak(upgradedSocket.getEnabledCipherSuites()));
234         }
235         if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
236             sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
237         }
238         upgradedSocket.setSSLParameters(sslParameters);
239 
240         final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
241         if (handshakeTimeout != null) {
242             upgradedSocket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
243         }
244 
245         initializeSocket(upgradedSocket);
246 
247         if (LOG.isDebugEnabled()) {
248             LOG.debug("Enabled protocols: {}", (Object) upgradedSocket.getEnabledProtocols());
249             LOG.debug("Enabled cipher suites: {}", (Object) upgradedSocket.getEnabledCipherSuites());
250             LOG.debug("Starting handshake ({})", handshakeTimeout);
251         }
252         upgradedSocket.startHandshake();
253         verifySession(target, upgradedSocket.getSession());
254     }
255 
256     void verifySession(
257             final String hostname,
258             final SSLSession sslsession,
259             final HostnameVerifier hostnameVerifier) throws SSLException {
260 
261         if (LOG.isDebugEnabled()) {
262             LOG.debug("Secure session established");
263             LOG.debug(" negotiated protocol: {}", sslsession.getProtocol());
264             LOG.debug(" negotiated cipher suite: {}", sslsession.getCipherSuite());
265 
266             try {
267 
268                 final Certificate[] certs = sslsession.getPeerCertificates();
269                 final Certificate cert = certs[0];
270                 if (cert instanceof X509Certificate) {
271                     final X509Certificate x509 = (X509Certificate) cert;
272                     final X500Principal peer = x509.getSubjectX500Principal();
273 
274                     LOG.debug(" peer principal: {}", peer);
275                     final Collection<List<?>> altNames1 = x509.getSubjectAlternativeNames();
276                     if (altNames1 != null) {
277                         final List<String> altNames = new ArrayList<>();
278                         for (final List<?> aC : altNames1) {
279                             if (!aC.isEmpty()) {
280                                 altNames.add(Objects.toString(aC.get(1), null));
281                             }
282                         }
283                         LOG.debug(" peer alternative names: {}", altNames);
284                     }
285 
286                     final X500Principal issuer = x509.getIssuerX500Principal();
287                     LOG.debug(" issuer principal: {}", issuer);
288                     final Collection<List<?>> altNames2 = x509.getIssuerAlternativeNames();
289                     if (altNames2 != null) {
290                         final List<String> altNames = new ArrayList<>();
291                         for (final List<?> aC : altNames2) {
292                             if (!aC.isEmpty()) {
293                                 altNames.add(Objects.toString(aC.get(1), null));
294                             }
295                         }
296                         LOG.debug(" issuer alternative names: {}", altNames);
297                     }
298                 }
299             } catch (final Exception ignore) {
300             }
301         }
302 
303         if (hostnameVerifier != null) {
304             final Certificate[] certs = sslsession.getPeerCertificates();
305             if (certs.length < 1) {
306                 throw new SSLPeerUnverifiedException("Peer certificate chain is empty");
307             }
308             final Certificate peerCertificate = certs[0];
309             final X509Certificate x509Certificate;
310             if (peerCertificate instanceof X509Certificate) {
311                 x509Certificate = (X509Certificate) peerCertificate;
312             } else {
313                 throw new SSLPeerUnverifiedException("Unexpected certificate type: " + peerCertificate.getType());
314             }
315             if (hostnameVerifier instanceof HttpClientHostnameVerifier) {
316                 ((HttpClientHostnameVerifier) hostnameVerifier).verify(hostname, x509Certificate);
317             } else if (!hostnameVerifier.verify(hostname, sslsession)) {
318                 final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509Certificate);
319                 throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
320                         "of the subject alternative names: " + subjectAlts);
321             }
322         }
323     }
324 
325 }