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