From bbd724f65fc501efede69564a1d413f823c8a38e Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Tue, 29 Oct 2013 23:02:10 +0100 Subject: [PATCH] SONAR-4741 client must ignore certificates on HTTPS connections --- .../org/sonar/api/utils/HttpDownloader.java | 2 + .../java/org/sonar/api/utils/HttpsTrust.java | 95 ++++++++++++++++ .../org/sonar/api/utils/HttpsTrustTest.java | 102 ++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpsTrust.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpsTrustTest.java diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java index 32d4044781e..c00792c4bfa 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java @@ -251,6 +251,8 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo LoggerFactory.getLogger(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri, ProxySelector.getDefault()) + ")"); HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + HttpsTrust.INSTANCE.trust(connection); + // allow both GZip and Deflate (ZLib) encodings connection.setRequestProperty("Accept-Encoding", "gzip"); if (!Strings.isNullOrEmpty(login)) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpsTrust.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpsTrust.java new file mode 100644 index 00000000000..d07f3690d35 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpsTrust.java @@ -0,0 +1,95 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.utils; + +import javax.net.ssl.*; +import java.net.HttpURLConnection; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +/** + * @since 4.0 + */ +class HttpsTrust { + + static HttpsTrust INSTANCE = new HttpsTrust(new SslContext()); + + static class SslContext { + SSLSocketFactory newFactory(TrustManager... managers) throws NoSuchAlgorithmException, KeyManagementException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, managers, new SecureRandom()); + return context.getSocketFactory(); + } + } + + private final SSLSocketFactory socketFactory; + private final HostnameVerifier hostnameVerifier; + + HttpsTrust(SslContext context) { + this.socketFactory = createSocketFactory(context); + this.hostnameVerifier = createHostnameVerifier(); + } + + void trust(HttpURLConnection connection) { + if (connection instanceof HttpsURLConnection) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; + httpsConnection.setSSLSocketFactory(socketFactory); + httpsConnection.setHostnameVerifier(hostnameVerifier); + } + } + + /** + * Trust all certificates + */ + private SSLSocketFactory createSocketFactory(SslContext context) { + try { + return context.newFactory(new AlwaysTrustManager()); + } catch (Exception e) { + throw new IllegalStateException("Fail to build SSL factory", e); + } + } + + /** + * Trust all hosts + */ + private HostnameVerifier createHostnameVerifier() { + return new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + } + + static class AlwaysTrustManager implements X509TrustManager { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Do not check + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Do not check + } + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpsTrustTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpsTrustTest.java new file mode 100644 index 00000000000..494b504a734 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpsTrustTest.java @@ -0,0 +1,102 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.utils; + +import org.junit.Test; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class HttpsTrustTest { + @Test + public void trustAllHosts() throws Exception { + HttpsURLConnection connection = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection); + + assertThat(connection.getHostnameVerifier()).isNotNull(); + assertThat(connection.getHostnameVerifier().verify("foo", null)).isTrue(); + } + + @Test + public void singleHostnameVerifier() throws Exception { + HttpsURLConnection connection1 = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection1); + HttpsURLConnection connection2 = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection2); + + assertThat(connection1.getHostnameVerifier()).isSameAs(connection2.getHostnameVerifier()); + } + + @Test + public void trustAllCerts() throws Exception { + HttpsURLConnection connection1 = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection1); + + assertThat(connection1.getSSLSocketFactory()).isNotNull(); + assertThat(connection1.getSSLSocketFactory().getDefaultCipherSuites()).isNotEmpty(); + } + + @Test + public void singleSslFactory() throws Exception { + HttpsURLConnection connection1 = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection1); + HttpsURLConnection connection2 = newHttpsConnection(); + HttpsTrust.INSTANCE.trust(connection2); + + assertThat(connection1.getSSLSocketFactory()).isSameAs(connection2.getSSLSocketFactory()); + } + + @Test + public void testAlwaysTrustManager() throws Exception { + HttpsTrust.AlwaysTrustManager manager = new HttpsTrust.AlwaysTrustManager(); + assertThat(manager.getAcceptedIssuers()).isEmpty(); + // does nothing + manager.checkClientTrusted(null, null); + manager.checkServerTrusted(null, null); + } + + @Test + public void failOnError() throws Exception { + HttpsTrust.SslContext context = mock(HttpsTrust.SslContext.class); + KeyManagementException cause = new KeyManagementException("foo"); + when(context.newFactory(any(TrustManager.class))).thenThrow(cause); + + try { + new HttpsTrust(context); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).isEqualTo("Fail to build SSL factory"); + assertThat(e.getCause()).isSameAs(cause); + } + } + + private HttpsURLConnection newHttpsConnection() throws IOException { + return (HttpsURLConnection) new URL("https://localhost").openConnection(); + } +} -- 2.39.5