SSLContext.getInstance("TLS") by default behaves differently on IBM JDK than on Oracle or OpenJDK.[1] On IBM JDK one gets sockets that have only TLSv1 enabled, which makes HTTPS connections fail since most servers refuse this old protocol version. On Oracle JDK/OpenJDK, one gets sockets with all available protocol versions enabled. Explicitly enable all available TLS protocol versions to make HTTPS connections work also on IBM JDK. [1] https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html#matchsslcontext_tls Bug: 558709 Change-Id: I5ffc57a78e67a6239b9dad54840a49a8ed28930a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.7.0.202001151323-m1
@@ -19,10 +19,12 @@ Import-Package: org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.scheme;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.socket;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.ssl;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.util;version="[4.3.0,5.0.0)", | |||
org.apache.http.entity;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.conn;version="[4.3.0,5.0.0)", | |||
org.apache.http.params;version="[4.3.0,5.0.0)", | |||
org.apache.http.ssl;version="[4.3.0,5.0.0)", | |||
org.eclipse.jgit.annotations;version="[5.7.0,5.8.0)", | |||
org.eclipse.jgit.nls;version="[5.7.0,5.8.0)", | |||
org.eclipse.jgit.transport.http;version="[5.7.0,5.8.0)", |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com> | |||
* Copyright (C) 2013, 2020 Christian Halstrick <christian.halstrick@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
@@ -69,6 +69,7 @@ import java.util.stream.Collectors; | |||
import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.KeyManager; | |||
import javax.net.ssl.SSLContext; | |||
import javax.net.ssl.SSLSocket; | |||
import javax.net.ssl.TrustManager; | |||
import org.apache.http.Header; | |||
@@ -89,14 +90,18 @@ import org.apache.http.config.Registry; | |||
import org.apache.http.config.RegistryBuilder; | |||
import org.apache.http.conn.socket.ConnectionSocketFactory; | |||
import org.apache.http.conn.socket.PlainConnectionSocketFactory; | |||
import org.apache.http.conn.ssl.DefaultHostnameVerifier; | |||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.apache.http.conn.util.PublicSuffixMatcherLoader; | |||
import org.apache.http.impl.client.HttpClientBuilder; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.impl.client.SystemDefaultCredentialsProvider; | |||
import org.apache.http.impl.conn.BasicHttpClientConnectionManager; | |||
import org.apache.http.ssl.SSLContexts; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.transport.http.HttpConnection; | |||
import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText; | |||
import org.eclipse.jgit.util.HttpSupport; | |||
import org.eclipse.jgit.util.TemporaryBuffer; | |||
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; | |||
@@ -153,10 +158,11 @@ public class HttpClientConnection implements HttpConnection { | |||
configBuilder | |||
.setRedirectsEnabled(followRedirects.booleanValue()); | |||
} | |||
SSLConnectionSocketFactory sslConnectionFactory = getSSLSocketFactory(); | |||
clientBuilder.setSSLSocketFactory(sslConnectionFactory); | |||
if (hostnameverifier != null) { | |||
SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory( | |||
getSSLContext(), hostnameverifier); | |||
clientBuilder.setSSLSocketFactory(sslConnectionFactory); | |||
// Using a custom verifier: we don't want pooled connections | |||
// with this. | |||
Registry<ConnectionSocketFactory> registry = RegistryBuilder | |||
.<ConnectionSocketFactory> create() | |||
.register("https", sslConnectionFactory) | |||
@@ -174,6 +180,32 @@ public class HttpClientConnection implements HttpConnection { | |||
return client; | |||
} | |||
private SSLConnectionSocketFactory getSSLSocketFactory() { | |||
HostnameVerifier verifier = hostnameverifier; | |||
SSLContext context; | |||
if (verifier == null) { | |||
// Use defaults | |||
context = SSLContexts.createDefault(); | |||
verifier = new DefaultHostnameVerifier( | |||
PublicSuffixMatcherLoader.getDefault()); | |||
} else { | |||
// Using a custom verifier. Attention: configure() must have been | |||
// called already, otherwise one gets a "context not initialized" | |||
// exception. In JGit this branch is reached only when hostname | |||
// verification is switched off, and JGit _does_ call configure() | |||
// before we get here. | |||
context = getSSLContext(); | |||
} | |||
return new SSLConnectionSocketFactory(context, verifier) { | |||
@Override | |||
protected void prepareSocket(SSLSocket socket) throws IOException { | |||
super.prepareSocket(socket); | |||
HttpSupport.configureTLS(socket); | |||
} | |||
}; | |||
} | |||
private SSLContext getSSLContext() { | |||
if (ctx == null) { | |||
try { |
@@ -282,6 +282,7 @@ expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1} | |||
expectedReportForRefNotReceived={0}: expected report for ref {1} not received | |||
failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1} | |||
failedCreateLockFile=Creating lock file {} failed | |||
failedReadHttpsProtocols=Failed to read system property https.protocols, assuming it is not set | |||
failedToConvert=Failed to convert rest: %s | |||
failedToDetermineFilterDefinition=An exception occurred while determining filter definitions | |||
failedUpdatingRefs=failed updating refs |
@@ -343,6 +343,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String expectedReportForRefNotReceived; | |||
/***/ public String failedAtomicFileCreation; | |||
/***/ public String failedCreateLockFile; | |||
/***/ public String failedReadHttpsProtocols; | |||
/***/ public String failedToDetermineFilterDefinition; | |||
/***/ public String failedToConvert; | |||
/***/ public String failedUpdatingRefs; |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com> | |||
* Copyright (C) 2013, 2020 Christian Halstrick <christian.halstrick@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
@@ -61,9 +61,12 @@ import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.HttpsURLConnection; | |||
import javax.net.ssl.KeyManager; | |||
import javax.net.ssl.SSLContext; | |||
import javax.net.ssl.SSLSocket; | |||
import javax.net.ssl.TrustManager; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.transport.internal.DelegatingSSLSocketFactory; | |||
import org.eclipse.jgit.util.HttpSupport; | |||
/** | |||
* A {@link org.eclipse.jgit.transport.http.HttpConnection} which simply | |||
* delegates every call to a {@link java.net.HttpURLConnection}. This is the | |||
@@ -265,7 +268,15 @@ public class JDKHttpConnection implements HttpConnection { | |||
KeyManagementException { | |||
SSLContext ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$ | |||
ctx.init(km, tm, random); | |||
((HttpsURLConnection) wrappedUrlConnection).setSSLSocketFactory(ctx | |||
.getSocketFactory()); | |||
((HttpsURLConnection) wrappedUrlConnection).setSSLSocketFactory( | |||
new DelegatingSSLSocketFactory(ctx.getSocketFactory()) { | |||
@Override | |||
protected void configure(SSLSocket socket) | |||
throws IOException { | |||
HttpSupport.configureTLS(socket); | |||
} | |||
}); | |||
} | |||
} |
@@ -0,0 +1,101 @@ | |||
/* | |||
* Copyright (c) 2020 Thomas Wolf <thomas.wolf@paranor.ch> | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.transport.internal; | |||
import java.io.IOException; | |||
import java.net.InetAddress; | |||
import java.net.Socket; | |||
import javax.net.ssl.SSLSocket; | |||
import javax.net.ssl.SSLSocketFactory; | |||
/** | |||
* An {@link SSLSocketFactory} that delegates to another factory and allows | |||
* configuring the created socket via {@link #configure(SSLSocket)} before it is | |||
* returned. | |||
*/ | |||
public abstract class DelegatingSSLSocketFactory extends SSLSocketFactory { | |||
private final SSLSocketFactory delegate; | |||
/** | |||
* Creates a new {@link DelegatingSSLSocketFactory} based on the given | |||
* delegate. | |||
* | |||
* @param delegate | |||
* {@link SSLSocketFactory} to delegate to | |||
*/ | |||
public DelegatingSSLSocketFactory(SSLSocketFactory delegate) { | |||
this.delegate = delegate; | |||
} | |||
@Override | |||
public SSLSocket createSocket() throws IOException { | |||
return prepare(delegate.createSocket()); | |||
} | |||
@Override | |||
public SSLSocket createSocket(String host, int port) throws IOException { | |||
return prepare(delegate.createSocket(host, port)); | |||
} | |||
@Override | |||
public SSLSocket createSocket(String host, int port, | |||
InetAddress localAddress, int localPort) throws IOException { | |||
return prepare( | |||
delegate.createSocket(host, port, localAddress, localPort)); | |||
} | |||
@Override | |||
public SSLSocket createSocket(InetAddress host, int port) | |||
throws IOException { | |||
return prepare(delegate.createSocket(host, port)); | |||
} | |||
@Override | |||
public SSLSocket createSocket(InetAddress host, int port, | |||
InetAddress localAddress, int localPort) throws IOException { | |||
return prepare( | |||
delegate.createSocket(host, port, localAddress, localPort)); | |||
} | |||
@Override | |||
public SSLSocket createSocket(Socket socket, String host, int port, | |||
boolean autoClose) throws IOException { | |||
return prepare(delegate.createSocket(socket, host, port, autoClose)); | |||
} | |||
@Override | |||
public String[] getDefaultCipherSuites() { | |||
return delegate.getDefaultCipherSuites(); | |||
} | |||
@Override | |||
public String[] getSupportedCipherSuites() { | |||
return delegate.getSupportedCipherSuites(); | |||
} | |||
private SSLSocket prepare(Socket socket) throws IOException { | |||
SSLSocket sslSocket = (SSLSocket) socket; | |||
configure(sslSocket); | |||
return sslSocket; | |||
} | |||
/** | |||
* Configure the newly created socket. | |||
* | |||
* @param socket | |||
* to configure | |||
* @throws IOException | |||
* if the socket cannot be configured | |||
*/ | |||
protected abstract void configure(SSLSocket socket) throws IOException; | |||
} |
@@ -59,19 +59,29 @@ import java.security.KeyManagementException; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.cert.X509Certificate; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.LinkedHashSet; | |||
import java.util.Set; | |||
import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.SSLSession; | |||
import javax.net.ssl.SSLSocket; | |||
import javax.net.ssl.TrustManager; | |||
import javax.net.ssl.X509TrustManager; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.transport.http.HttpConnection; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
/** | |||
* Extra utilities to support usage of HTTP. | |||
*/ | |||
public class HttpSupport { | |||
private final static Logger LOG = LoggerFactory | |||
.getLogger(HttpSupport.class); | |||
/** The {@code GET} HTTP method. */ | |||
public static final String METHOD_GET = "GET"; //$NON-NLS-1$ | |||
@@ -191,6 +201,8 @@ public class HttpSupport { | |||
*/ | |||
public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$ | |||
private static Set<String> configuredHttpsProtocols; | |||
/** | |||
* URL encode a value string into an output buffer. | |||
* | |||
@@ -359,6 +371,92 @@ public class HttpSupport { | |||
} | |||
} | |||
/** | |||
* Enables all supported TLS protocol versions on the socket given. If | |||
* system property "https.protocols" is set, only protocols specified there | |||
* are enabled. | |||
* <p> | |||
* This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK | |||
* returns sockets that support all TLS protocol versions but have only the | |||
* one specified in the context enabled. Oracle or OpenJDK return sockets | |||
* that have all available protocols enabled already, up to the one | |||
* specified. | |||
* <p> | |||
* <table> | |||
* <tr> | |||
* <td>SSLContext.getInstance()</td> | |||
* <td>OpenJDK</td> | |||
* <td>IDM JDK</td> | |||
* </tr> | |||
* <tr> | |||
* <td>"TLS"</td> | |||
* <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br /> | |||
* Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td> | |||
* <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> | |||
* Enabled: TLSv1</td> | |||
* </tr> | |||
* <tr> | |||
* <td>"TLSv1.2"</td> | |||
* <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> | |||
* Enabled: TLSv1, TLSV1.1, TLSv1.2</td> | |||
* <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> | |||
* Enabled: TLSv1.2</td> | |||
* </tr> | |||
* </table> | |||
* | |||
* @param socket | |||
* to configure | |||
* @see <a href= | |||
* "https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html">Behavior | |||
* of SSLContext.getInstance("TLS") on IBM JDK</a> | |||
* @see <a href= | |||
* "https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization">Customizing | |||
* JSSE about https.protocols</a> | |||
* @since 5.7 | |||
*/ | |||
public static void configureTLS(SSLSocket socket) { | |||
// 1. Enable all available TLS protocol versions | |||
Set<String> enabled = new LinkedHashSet<>( | |||
Arrays.asList(socket.getEnabledProtocols())); | |||
for (String s : socket.getSupportedProtocols()) { | |||
if (s.startsWith("TLS")) { //$NON-NLS-1$ | |||
enabled.add(s); | |||
} | |||
} | |||
// 2. Respect the https.protocols system property | |||
Set<String> configured = getConfiguredProtocols(); | |||
if (!configured.isEmpty()) { | |||
enabled.retainAll(configured); | |||
} | |||
if (!enabled.isEmpty()) { | |||
socket.setEnabledProtocols(enabled.toArray(new String[0])); | |||
} | |||
} | |||
private static Set<String> getConfiguredProtocols() { | |||
Set<String> result = configuredHttpsProtocols; | |||
if (result == null) { | |||
String configured = getProperty("https.protocols"); //$NON-NLS-1$ | |||
if (StringUtils.isEmptyOrNull(configured)) { | |||
result = Collections.emptySet(); | |||
} else { | |||
result = new LinkedHashSet<>( | |||
Arrays.asList(configured.split("\\s*,\\s*"))); //$NON-NLS-1$ | |||
} | |||
configuredHttpsProtocols = result; | |||
} | |||
return result; | |||
} | |||
private static String getProperty(String property) { | |||
try { | |||
return SystemReader.getInstance().getProperty(property); | |||
} catch (SecurityException e) { | |||
LOG.warn(JGitText.get().failedReadHttpsProtocols, e); | |||
return null; | |||
} | |||
} | |||
private HttpSupport() { | |||
// Utility class only. | |||
} |