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.net.NamedEndpoint;
66  import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
67  import org.apache.hc.core5.reactor.ssl.TlsDetails;
68  import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer;
69  import org.apache.hc.core5.util.Args;
70  import org.apache.hc.core5.util.Timeout;
71  import org.slf4j.Logger;
72  import org.slf4j.LoggerFactory;
73  
74  @Contract(threading = ThreadingBehavior.STATELESS)
75  abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrategy {
76  
77      private static final Logger LOG = LoggerFactory.getLogger(AbstractClientTlsStrategy.class);
78  
79      private final SSLContext sslContext;
80      private final String[] supportedProtocols;
81      private final String[] supportedCipherSuites;
82      private final SSLBufferMode sslBufferManagement;
83      private final HostnameVerificationPolicy hostnameVerificationPolicy;
84      private final HostnameVerifier hostnameVerifier;
85  
86      AbstractClientTlsStrategy(
87              final SSLContext sslContext,
88              final String[] supportedProtocols,
89              final String[] supportedCipherSuites,
90              final SSLBufferMode sslBufferManagement,
91              final HostnameVerificationPolicy hostnameVerificationPolicy,
92              final HostnameVerifier hostnameVerifier) {
93          super();
94          this.sslContext = Args.notNull(sslContext, "SSL context");
95          this.supportedProtocols = supportedProtocols;
96          this.supportedCipherSuites = supportedCipherSuites;
97          this.sslBufferManagement = sslBufferManagement != null ? sslBufferManagement : SSLBufferMode.STATIC;
98          this.hostnameVerificationPolicy = hostnameVerificationPolicy != null ? hostnameVerificationPolicy : HostnameVerificationPolicy.BOTH;
99          this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier :
100                 (this.hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN ? NoopHostnameVerifier.INSTANCE : HttpsSupport.getDefaultHostnameVerifier());
101     }
102 
103     /**
104      * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)}
105      */
106     @Deprecated
107     @Override
108     public boolean upgrade(
109             final TransportSecurityLayer tlsSession,
110             final HttpHost host,
111             final SocketAddress localAddress,
112             final SocketAddress remoteAddress,
113             final Object attachment,
114             final Timeout handshakeTimeout) {
115         upgrade(tlsSession, host, attachment, handshakeTimeout, null);
116         return true;
117     }
118 
119     @Override
120     public void upgrade(
121             final TransportSecurityLayer tlsSession,
122             final NamedEndpoint endpoint,
123             final Object attachment,
124             final Timeout handshakeTimeout,
125             final FutureCallback<TransportSecurityLayer> callback) {
126         tlsSession.startTls(sslContext, endpoint, sslBufferManagement, (e, sslEngine) -> {
127 
128             final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
129             final HttpVersionPolicy versionPolicy = tlsConfig.getHttpVersionPolicy();
130 
131             final SSLParameters sslParameters = sslEngine.getSSLParameters();
132             final String[] supportedProtocols = tlsConfig.getSupportedProtocols();
133             if (supportedProtocols != null) {
134                 sslParameters.setProtocols(supportedProtocols);
135             } else if (this.supportedProtocols != null) {
136                 sslParameters.setProtocols(this.supportedProtocols);
137             } else if (versionPolicy != HttpVersionPolicy.FORCE_HTTP_1) {
138                 sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols()));
139             }
140             final String[] supportedCipherSuites = tlsConfig.getSupportedCipherSuites();
141             if (supportedCipherSuites != null) {
142                 sslParameters.setCipherSuites(supportedCipherSuites);
143             } else if (this.supportedCipherSuites != null) {
144                 sslParameters.setCipherSuites(this.supportedCipherSuites);
145             } else if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) {
146                 sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites()));
147             }
148 
149             if (versionPolicy != HttpVersionPolicy.FORCE_HTTP_1) {
150                 H2TlsSupport.setEnableRetransmissions(sslParameters, false);
151             }
152 
153             applyParameters(sslEngine, sslParameters, H2TlsSupport.selectApplicationProtocols(versionPolicy));
154 
155             if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
156                 sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
157             }
158 
159             initializeEngine(sslEngine);
160 
161             if (LOG.isDebugEnabled()) {
162                 LOG.debug("Enabled protocols: {}", Arrays.asList(sslEngine.getEnabledProtocols()));
163                 LOG.debug("Enabled cipher suites:{}", Arrays.asList(sslEngine.getEnabledCipherSuites()));
164                 LOG.debug("Starting handshake ({})", handshakeTimeout);
165             }
166         }, (e, sslEngine) -> {
167             verifySession(endpoint.getHostName(), sslEngine.getSession());
168             final TlsDetails tlsDetails = createTlsDetails(sslEngine);
169             final String negotiatedCipherSuite = sslEngine.getSession().getCipherSuite();
170             if (tlsDetails != null && ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
171                 if (TlsCiphers.isH2Blacklisted(negotiatedCipherSuite)) {
172                     throw new SSLHandshakeException("Cipher suite `" + negotiatedCipherSuite
173                         + "` does not provide adequate security for HTTP/2");
174                 }
175             }
176             return tlsDetails;
177         }, handshakeTimeout, callback);
178     }
179 
180     abstract void applyParameters(SSLEngine sslEngine, SSLParameters sslParameters, String[] appProtocols);
181 
182     abstract TlsDetails createTlsDetails(SSLEngine sslEngine);
183 
184     protected void initializeEngine(final SSLEngine sslEngine) {
185     }
186 
187     protected void initializeSocket(final SSLSocket socket) {
188     }
189 
190     protected void verifySession(
191             final String hostname,
192             final SSLSession sslsession) throws SSLException {
193         verifySession(hostname, sslsession,
194                 hostnameVerificationPolicy == HostnameVerificationPolicy.CLIENT || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH ? hostnameVerifier : null);
195     }
196 
197     @Override
198     public SSLSocket upgrade(final Socket socket,
199                              final String target,
200                              final int port,
201                              final Object attachment,
202                              final HttpContext context) throws IOException {
203         final SSLSocket upgradedSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(
204                 socket,
205                 target,
206                 port,
207                 true);
208         executeHandshake(upgradedSocket, target, attachment);
209         return upgradedSocket;
210     }
211 
212     private void executeHandshake(
213             final SSLSocket upgradedSocket,
214             final String target,
215             final Object attachment) throws IOException {
216         final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
217 
218         final SSLParameters sslParameters = upgradedSocket.getSSLParameters();
219         if (supportedProtocols != null) {
220             sslParameters.setProtocols(supportedProtocols);
221         } else {
222             sslParameters.setProtocols((TLS.excludeWeak(upgradedSocket.getEnabledProtocols())));
223         }
224         if (supportedCipherSuites != null) {
225             sslParameters.setCipherSuites(supportedCipherSuites);
226         } else {
227             sslParameters.setCipherSuites(TlsCiphers.excludeWeak(upgradedSocket.getEnabledCipherSuites()));
228         }
229         if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
230             sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
231         }
232         upgradedSocket.setSSLParameters(sslParameters);
233 
234         final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
235         if (handshakeTimeout != null) {
236             upgradedSocket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
237         }
238 
239         initializeSocket(upgradedSocket);
240 
241         if (LOG.isDebugEnabled()) {
242             LOG.debug("Enabled protocols: {}", (Object) upgradedSocket.getEnabledProtocols());
243             LOG.debug("Enabled cipher suites: {}", (Object) upgradedSocket.getEnabledCipherSuites());
244             LOG.debug("Starting handshake ({})", handshakeTimeout);
245         }
246         upgradedSocket.startHandshake();
247         verifySession(target, upgradedSocket.getSession());
248     }
249 
250     void verifySession(
251             final String hostname,
252             final SSLSession sslsession,
253             final HostnameVerifier hostnameVerifier) throws SSLException {
254 
255         if (LOG.isDebugEnabled()) {
256             LOG.debug("Secure session established");
257             LOG.debug(" negotiated protocol: {}", sslsession.getProtocol());
258             LOG.debug(" negotiated cipher suite: {}", sslsession.getCipherSuite());
259 
260             try {
261 
262                 final Certificate[] certs = sslsession.getPeerCertificates();
263                 final Certificate cert = certs[0];
264                 if (cert instanceof X509Certificate) {
265                     final X509Certificate x509 = (X509Certificate) cert;
266                     final X500Principal peer = x509.getSubjectX500Principal();
267 
268                     LOG.debug(" peer principal: {}", peer);
269                     final Collection<List<?>> altNames1 = x509.getSubjectAlternativeNames();
270                     if (altNames1 != null) {
271                         final List<String> altNames = new ArrayList<>();
272                         for (final List<?> aC : altNames1) {
273                             if (!aC.isEmpty()) {
274                                 altNames.add(Objects.toString(aC.get(1), null));
275                             }
276                         }
277                         LOG.debug(" peer alternative names: {}", altNames);
278                     }
279 
280                     final X500Principal issuer = x509.getIssuerX500Principal();
281                     LOG.debug(" issuer principal: {}", issuer);
282                     final Collection<List<?>> altNames2 = x509.getIssuerAlternativeNames();
283                     if (altNames2 != null) {
284                         final List<String> altNames = new ArrayList<>();
285                         for (final List<?> aC : altNames2) {
286                             if (!aC.isEmpty()) {
287                                 altNames.add(Objects.toString(aC.get(1), null));
288                             }
289                         }
290                         LOG.debug(" issuer alternative names: {}", altNames);
291                     }
292                 }
293             } catch (final Exception ignore) {
294             }
295         }
296 
297         if (hostnameVerifier != null) {
298             final Certificate[] certs = sslsession.getPeerCertificates();
299             if (certs.length < 1) {
300                 throw new SSLPeerUnverifiedException("Peer certificate chain is empty");
301             }
302             final Certificate peerCertificate = certs[0];
303             final X509Certificate x509Certificate;
304             if (peerCertificate instanceof X509Certificate) {
305                 x509Certificate = (X509Certificate) peerCertificate;
306             } else {
307                 throw new SSLPeerUnverifiedException("Unexpected certificate type: " + peerCertificate.getType());
308             }
309             if (hostnameVerifier instanceof HttpClientHostnameVerifier) {
310                 ((HttpClientHostnameVerifier) hostnameVerifier).verify(hostname, x509Certificate);
311             } else if (!hostnameVerifier.verify(hostname, sslsession)) {
312                 final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509Certificate);
313                 throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
314                         "of the subject alternative names: " + subjectAlts);
315             }
316         }
317     }
318 
319 }