]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8012 Restore support of client side certificates 1206/head
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 31 Aug 2016 21:24:03 +0000 (23:24 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Mon, 5 Sep 2016 10:05:55 +0000 (12:05 +0200)
it/it-tests/pom.xml
it/it-tests/src/test/java/it/Category3Suite.java
it/it-tests/src/test/java/it/analysis/SSLTest.java [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/README [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/clientkeystore.jks [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/clienttruststore.jks [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/openssl.cnf [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/serverkeystore.jks [new file with mode: 0644]
it/it-tests/src/test/resources/analysis/SSLTest/servertruststore.jks [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java

index f13d10e223cfa54421d994f481d9d8e1c3553867..ffd2d819fc5b79f86cff4702ff921dffb890e4e7 100644 (file)
@@ -14,6 +14,7 @@
   <properties>
     <category>*</category>
     <skipIts>false</skipIts>
+    <jetty.version>9.3.11.v20160721</jetty.version>
   </properties>
 
   <dependencies>
       <artifactId>subethasmtp</artifactId>
       <version>3.1.6</version>
     </dependency>
+    
+    <!-- SSL tests -->
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlet</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-proxy</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.sonarsource.scanner.cli</groupId>
+      <artifactId>sonar-scanner-cli</artifactId>
+      <version>2.7</version>
+      <type>zip</type>
+    </dependency>
   </dependencies>
 
   <build>
index 9a4d76d0ed38578839e6005a6a9a92096ddb51ed..97899f889369189c4ebbbf8d431acc350c6d8067 100644 (file)
@@ -30,6 +30,7 @@ import it.analysis.MultiLanguageTest;
 import it.analysis.ProjectBuilderTest;
 import it.analysis.ProjectProvisioningTest;
 import it.analysis.ReportDumpTest;
+import it.analysis.SSLTest;
 import it.analysis.SettingsEncryptionTest;
 import it.analysis.TempFolderTest;
 import it.measure.DecimalScaleMetricTest;
@@ -55,6 +56,7 @@ import static util.ItUtils.xooPlugin;
   IssuesModeTest.class,
   SettingsEncryptionTest.class,
   ReportDumpTest.class,
+  SSLTest.class,
   // measures
   DecimalScaleMetricTest.class
 })
diff --git a/it/it-tests/src/test/java/it/analysis/SSLTest.java b/it/it-tests/src/test/java/it/analysis/SSLTest.java
new file mode 100644 (file)
index 0000000..098cfa5
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 it.analysis;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.BuildResult;
+import com.sonar.orchestrator.build.SonarScanner;
+import com.sonar.orchestrator.util.NetworkUtils;
+import it.Category3Suite;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.proxy.ProxyServlet;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import util.ItUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SSLTest {
+
+  private static final String CLIENT_KEYSTORE = "/analysis/SSLTest/clientkeystore.jks";
+  private static final String CLIENT_KEYSTORE_PWD = "clientp12pwd";
+
+  private static final String CLIENT_TRUSTSTORE = "/analysis/SSLTest/clienttruststore.jks";
+  private static final String CLIENT_TRUSTSTORE_PWD = "clienttruststorepwd";
+
+  @ClassRule
+  public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR;
+
+  private static Server server;
+  private static int httpsPort;
+
+  @Before
+  public void deleteData() {
+    orchestrator.resetData();
+  }
+
+  public static void startSSLTransparentReverseProxy(boolean requireClientAuth) throws Exception {
+    int httpPort = NetworkUtils.getNextAvailablePort();
+    httpsPort = NetworkUtils.getNextAvailablePort();
+
+    // Setup Threadpool
+    QueuedThreadPool threadPool = new QueuedThreadPool();
+    threadPool.setMaxThreads(500);
+
+    server = new Server(threadPool);
+
+    // HTTP Configuration
+    HttpConfiguration httpConfig = new HttpConfiguration();
+    httpConfig.setSecureScheme("https");
+    httpConfig.setSecurePort(httpsPort);
+    httpConfig.setSendServerVersion(true);
+    httpConfig.setSendDateHeader(false);
+
+    // Handler Structure
+    HandlerCollection handlers = new HandlerCollection();
+    handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()});
+    server.setHandler(handlers);
+
+    ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+    http.setPort(httpPort);
+    server.addConnector(http);
+
+    Path serverKeyStore = Paths.get(SSLTest.class.getResource("/analysis/SSLTest/serverkeystore.jks").toURI()).toAbsolutePath();
+    String keyStorePassword = "serverkeystorepwd";
+    String serverKeyPassword = "serverp12pwd";
+    Path serverTrustStore = Paths.get(SSLTest.class.getResource("/analysis/SSLTest/servertruststore.jks").toURI()).toAbsolutePath();
+    String trustStorePassword = "servertruststorepwd";
+
+    // SSL Context Factory
+    SslContextFactory sslContextFactory = new SslContextFactory();
+    sslContextFactory.setKeyStorePath(serverKeyStore.toString());
+    sslContextFactory.setKeyStorePassword(keyStorePassword);
+    sslContextFactory.setKeyManagerPassword(serverKeyPassword);
+    sslContextFactory.setTrustStorePath(serverTrustStore.toString());
+    sslContextFactory.setTrustStorePassword(trustStorePassword);
+    sslContextFactory.setNeedClientAuth(requireClientAuth);
+    sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
+      "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+      "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+      "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+      "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+      "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+
+    // SSL HTTP Configuration
+    HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
+
+    // SSL Connector
+    ServerConnector sslConnector = new ServerConnector(server,
+      new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+      new HttpConnectionFactory(httpsConfig));
+    sslConnector.setPort(httpsPort);
+    server.addConnector(sslConnector);
+
+    server.start();
+  }
+
+  private static ServletContextHandler proxyHandler() {
+    ServletContextHandler contextHandler = new ServletContextHandler();
+    contextHandler.setServletHandler(newServletHandler());
+    return contextHandler;
+  }
+
+  private static ServletHandler newServletHandler() {
+    ServletHandler handler = new ServletHandler();
+    ServletHolder holder = handler.addServletWithMapping(ProxyServlet.Transparent.class, "/*");
+    holder.setInitParameter("proxyTo", orchestrator.getServer().getUrl());
+    return handler;
+  }
+
+  @After
+  public void stopProxy() throws Exception {
+    if (server != null && server.isStarted()) {
+      server.stop();
+    }
+  }
+
+  @Test
+  public void simple_analysis_with_server_and_client_certificate() throws Exception {
+    startSSLTransparentReverseProxy(true);
+
+    SonarScanner sonarScanner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample"))
+      .setScannerVersion("2.7")
+      .setProperty("sonar.host.url", "https://localhost:" + httpsPort);
+
+    BuildResult buildResult = orchestrator.executeBuildQuietly(sonarScanner);
+    assertThat(buildResult.getLastStatus()).isNotEqualTo(0);
+    assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
+
+    Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath();
+    String truststorePassword = CLIENT_TRUSTSTORE_PWD;
+    Path clientKeystore = Paths.get(SSLTest.class.getResource(CLIENT_KEYSTORE).toURI()).toAbsolutePath();
+    String keystorePassword = CLIENT_KEYSTORE_PWD;
+
+    buildResult = orchestrator.executeBuild(sonarScanner
+      .setEnvironmentVariable("SONAR_SCANNER_OPTS",
+        "-Djavax.net.ssl.trustStore=" + clientTruststore.toString() +
+          " -Djavax.net.ssl.trustStorePassword=" + truststorePassword +
+          " -Djavax.net.ssl.keyStore=" + clientKeystore.toString() +
+          " -Djavax.net.ssl.keyStorePassword=" + keystorePassword));
+  }
+
+  @Test
+  public void simple_analysis_with_server_certificate() throws Exception {
+    startSSLTransparentReverseProxy(false);
+
+    SonarScanner sonarScanner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample"))
+      .setScannerVersion("2.7")
+      .setProperty("sonar.host.url", "https://localhost:" + httpsPort);
+
+    BuildResult buildResult = orchestrator.executeBuildQuietly(sonarScanner);
+    assertThat(buildResult.getLastStatus()).isNotEqualTo(0);
+    assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException");
+
+    Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath();
+    String truststorePassword = CLIENT_TRUSTSTORE_PWD;
+
+    buildResult = orchestrator.executeBuild(sonarScanner
+      .setEnvironmentVariable("SONAR_SCANNER_OPTS",
+        "-Djavax.net.ssl.trustStore=" + clientTruststore.toString() +
+          " -Djavax.net.ssl.trustStorePassword=" + truststorePassword));
+  }
+}
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/README b/it/it-tests/src/test/resources/analysis/SSLTest/README
new file mode 100644 (file)
index 0000000..fb535a9
--- /dev/null
@@ -0,0 +1,184 @@
+$ openssl req -config ./openssl.cnf -newkey rsa:2048 -nodes -keyform PEM -keyout ca.key -x509 -days 3650 -extensions certauth -outform PEM -out ca.cer
+Generating a 2048 bit RSA private key
+..............................................+++
+........................................................................................................+++
+writing new private key to 'ca.key'
+-----
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country [FR]:
+Locality [Poitiers]:
+Organization [SonarSource]:
+Common Name []:CA for SonarQube ITs
+
+$ openssl genrsa -out server.key 2048
+Generating RSA private key, 2048 bit long modulus
+........................+++
+............................+++
+e is 65537 (0x10001)
+
+$ openssl req -config ./openssl.cnf -new -key server.key -out server.req
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country [FR]:
+Locality [Poitiers]:
+Organization [SonarSource]:
+Common Name []:localhost
+
+$ openssl x509 -req -in server.req -CA ca.cer -CAkey ca.key -set_serial 100 -extfile openssl.cnf -extensions server -days 3650 -outform PEM -out server.cer
+Signature ok
+subject=/C=FR/L=Poitiers/O=SonarSource/CN=localhost
+Getting CA Private Key
+
+$ openssl genrsa -out client.key 2048
+Generating RSA private key, 2048 bit long modulus
+...................................+++
+.....................................................+++
+e is 65537 (0x10001)
+
+$ openssl req -config ./openssl.cnf -new -key client.key -out client.req
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country [FR]:
+Locality [Poitiers]:
+Organization [SonarSource]:
+Common Name []:Julien Henry
+
+$ openssl x509 -req -in client.req -CA ca.cer -CAkey ca.key -set_serial 101 -extfile openssl.cnf -extensions client -days 3650 -outform PEM -out client.cer
+Signature ok
+subject=/C=FR/L=Poitiers/O=SonarSource/CN=Julien Henry
+Getting CA Private Key
+
+$ openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12
+Enter Export Password: clientp12pwd
+Verifying - Enter Export Password: clientp12pwd
+
+$ openssl pkcs12 -inkey server.key -in server.cer -export -out server.p12
+Enter Export Password: serverp12pwd
+Verifying - Enter Export Password: serverp12pwd
+
+$ keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore serverkeystore.jks
+Entrez le mot de passe du fichier de clés de destination : serverkeystorepwd 
+Ressaisissez le nouveau mot de passe : serverkeystorepwd
+Entrez le mot de passe du fichier de clés source : serverp12pwd 
+L'entrée de l'alias 1 a été importée.
+Commande d'import exécutée : 1 entrées importées, échec ou annulation de 0 entrées
+
+$ keytool -import -file ca.cer -keystore servertruststore.jks
+Entrez le mot de passe du fichier de clés : servertruststorepwd 
+Ressaisissez le nouveau mot de passe : servertruststorepwd
+Propriétaire : CN=Test CA, O=SonarSource, L=Poitiers, C=FR
+Emetteur : CN=Test CA, O=SonarSource, L=Poitiers, C=FR
+Numéro de série : dabbebc7bce2fc6a
+Valide du : Wed Aug 31 14:42:15 CEST 2016 au : Sat Aug 29 14:42:15 CEST 2026
+Empreintes du certificat :
+         MD5:  69:36:AE:65:51:CD:F4:C3:83:77:DE:45:AE:49:01:1A
+         SHA1 : 77:92:45:84:18:FC:34:7A:2A:B0:EC:3D:22:48:15:1A:19:71:1D:B3
+         SHA256 : 99:03:89:84:6E:E3:D3:B7:12:2D:70:7E:49:88:49:41:52:1C:89:3A:9B:C0:83:D1:C5:44:D4:93:FB:42:C8:07
+         Nom de l'algorithme de signature : SHA1withRSA
+         Version : 3
+
+Extensions : 
+
+#1: ObjectId: 2.5.29.35 Criticality=false
+AuthorityKeyIdentifier [
+KeyIdentifier [
+0000: 3A 61 C1 86 AD BE FC 15   82 B3 59 FF 00 28 5E F9  :a........Y..(^.
+0010: B5 5A 87 04                                        .Z..
+]
+[CN=Test CA, O=SonarSource, L=Poitiers, C=FR]
+SerialNumber: [    dabbebc7 bce2fc6a]
+]
+
+#2: ObjectId: 2.5.29.19 Criticality=false
+BasicConstraints:[
+  CA:true
+  PathLen:2147483647
+]
+
+#3: ObjectId: 2.5.29.31 Criticality=false
+CRLDistributionPoints [
+  [DistributionPoint:
+     [URIName: http://testca.local/ca.crl]
+]]
+
+#4: ObjectId: 2.5.29.14 Criticality=false
+SubjectKeyIdentifier [
+KeyIdentifier [
+0000: 3A 61 C1 86 AD BE FC 15   82 B3 59 FF 00 28 5E F9  :a........Y..(^.
+0010: B5 5A 87 04                                        .Z..
+]
+]
+
+Faire confiance à ce certificat ? [non] :  oui
+Certificat ajouté au fichier de clés
+
+$ keytool -import -file server.cer -keystore clienttruststore.jks
+Entrez le mot de passe du fichier de clés : clienttruststorepwd 
+Ressaisissez le nouveau mot de passe : clienttruststorepwd
+Propriétaire : CN=localhost, O=SonarSource, L=Poitiers, C=FR
+Emetteur : CN=Test CA, O=SonarSource, L=Poitiers, C=FR
+Numéro de série : 64
+Valide du : Wed Aug 31 14:45:30 CEST 2016 au : Thu Aug 31 14:45:30 CEST 2017
+Empreintes du certificat :
+         MD5:  40:52:F4:5E:67:C3:68:B6:00:7D:70:C0:1E:8E:75:2E
+         SHA1 : 83:3F:4F:AC:4E:E6:F4:06:14:01:E6:5B:F2:63:34:94:68:12:8C:3A
+         SHA256 : 7C:03:9A:CA:0D:B5:57:A5:66:56:75:09:23:45:9E:D5:CC:6C:72:14:0B:4B:9B:E8:29:3F:78:4C:9F:D6:77:E2
+         Nom de l'algorithme de signature : SHA256withRSA
+         Version : 3
+
+Extensions : 
+
+#1: ObjectId: 2.5.29.19 Criticality=false
+BasicConstraints:[
+  CA:false
+  PathLen: undefined
+]
+
+#2: ObjectId: 2.5.29.31 Criticality=false
+CRLDistributionPoints [
+  [DistributionPoint:
+     [URIName: http://testca.local/ca.crl]
+]]
+
+#3: ObjectId: 2.5.29.37 Criticality=false
+ExtendedKeyUsages [
+  serverAuth
+]
+
+#4: ObjectId: 2.5.29.15 Criticality=false
+KeyUsage [
+  DigitalSignature
+  Key_Encipherment
+  Data_Encipherment
+]
+
+#5: ObjectId: 2.16.840.1.113730.1.1 Criticality=false
+NetscapeCertType [
+   SSL server
+]
+
+Faire confiance à ce certificat ? [non] :  oui
+Certificat ajouté au fichier de clés
+
+$ keytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore clientkeystore.jks
+Entrez le mot de passe du fichier de clés de destination : clientp12pwd 
+Ressaisissez le nouveau mot de passe : clientp12pwd
+Entrez le mot de passe du fichier de clés source : clientp12pwd 
+L'entrée de l'alias 1 a été importée.
+Commande d'import exécutée : 1 entrées importées, échec ou annulation de 0 entrées
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/clientkeystore.jks b/it/it-tests/src/test/resources/analysis/SSLTest/clientkeystore.jks
new file mode 100644 (file)
index 0000000..653f3c7
Binary files /dev/null and b/it/it-tests/src/test/resources/analysis/SSLTest/clientkeystore.jks differ
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/clienttruststore.jks b/it/it-tests/src/test/resources/analysis/SSLTest/clienttruststore.jks
new file mode 100644 (file)
index 0000000..2529245
Binary files /dev/null and b/it/it-tests/src/test/resources/analysis/SSLTest/clienttruststore.jks differ
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/openssl.cnf b/it/it-tests/src/test/resources/analysis/SSLTest/openssl.cnf
new file mode 100644 (file)
index 0000000..4a5148f
--- /dev/null
@@ -0,0 +1,38 @@
+[ req ]
+default_md = sha1
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+countryName = Country
+countryName_default = FR
+countryName_min = 2
+countryName_max = 2
+localityName = Locality
+localityName_default = Poitiers
+organizationName = Organization
+organizationName_default = SonarSource
+commonName = Common Name
+commonName_max = 64
+
+[ certauth ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+basicConstraints = CA:true
+crlDistributionPoints = @crl
+
+[ server ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature, keyEncipherment, dataEncipherment
+extendedKeyUsage = serverAuth
+nsCertType = server
+crlDistributionPoints = @crl
+
+[ client ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature, keyEncipherment, dataEncipherment
+extendedKeyUsage = clientAuth
+nsCertType = client
+crlDistributionPoints = @crl
+
+[ crl ]
+URI=http://testca.local/ca.crl
\ No newline at end of file
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/serverkeystore.jks b/it/it-tests/src/test/resources/analysis/SSLTest/serverkeystore.jks
new file mode 100644 (file)
index 0000000..319c899
Binary files /dev/null and b/it/it-tests/src/test/resources/analysis/SSLTest/serverkeystore.jks differ
diff --git a/it/it-tests/src/test/resources/analysis/SSLTest/servertruststore.jks b/it/it-tests/src/test/resources/analysis/SSLTest/servertruststore.jks
new file mode 100644 (file)
index 0000000..a3aee3b
Binary files /dev/null and b/it/it-tests/src/test/resources/analysis/SSLTest/servertruststore.jks differ
index 1231c6c7069412ee32f0e79a8dcfaaad947e5cca..3091c40faf869e4417d1ced4d19d4daa54ba432a 100644 (file)
  */
 package org.sonarqube.ws.client;
 
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.Proxy;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
 import okhttp3.Call;
 import okhttp3.ConnectionSpec;
 import okhttp3.Credentials;
@@ -52,6 +63,8 @@ public class HttpConnector implements WsConnector {
 
   public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30_000;
   public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60_000;
+  private static final String NONE = "NONE";
+  private static final String P11KEYSTORE = "PKCS11";
 
   /**
    * Base URL with trailing slash, for instance "https://localhost/sonarqube/".
@@ -100,10 +113,107 @@ public class HttpConnector implements WsConnector {
       .supportsTlsExtensions(true)
       .build();
     okHttpClientBuilder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
+    X509TrustManager systemDefaultTrustManager = systemDefaultTrustManager();
+    okHttpClientBuilder.sslSocketFactory(systemDefaultSslSocketFactory(systemDefaultTrustManager), systemDefaultTrustManager);
 
     return okHttpClientBuilder.build();
   }
 
+  private static X509TrustManager systemDefaultTrustManager() {
+    try {
+      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+      trustManagerFactory.init((KeyStore) null);
+      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+      if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+        throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
+      }
+      return (X509TrustManager) trustManagers[0];
+    } catch (GeneralSecurityException e) {
+      // The system has no TLS. Just give up.
+      throw new AssertionError(e);
+    }
+  }
+
+  private static SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
+    KeyManager[] defaultKeyManager;
+    try {
+      defaultKeyManager = getDefaultKeyManager();
+    } catch (Exception e) {
+      throw new IllegalStateException("Unable to get default key manager", e);
+    }
+    try {
+      SSLContext sslContext = SSLContext.getInstance("TLS");
+      sslContext.init(defaultKeyManager, new TrustManager[] {trustManager}, null);
+      return sslContext.getSocketFactory();
+    } catch (GeneralSecurityException e) {
+      // The system has no TLS. Just give up.
+      throw new AssertionError(e);
+    }
+  }
+
+  private static void logDebug(String msg) {
+    boolean debugEnabled = "all".equals(System.getProperty("javax.net.debug"));
+    if (debugEnabled) {
+      System.out.println(msg);
+    }
+  }
+
+  /**
+   * Inspired from sun.security.ssl.SSLContextImpl#getDefaultKeyManager()
+   */
+  private static synchronized KeyManager[] getDefaultKeyManager() throws Exception {
+
+    final String defaultKeyStore = System.getProperty("javax.net.ssl.keyStore", "");
+    String defaultKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
+    String defaultKeyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");
+
+    logDebug("keyStore is : " + defaultKeyStore);
+    logDebug("keyStore type is : " + defaultKeyStoreType);
+    logDebug("keyStore provider is : " + defaultKeyStoreProvider);
+
+    if (P11KEYSTORE.equals(defaultKeyStoreType) && !NONE.equals(defaultKeyStore)) {
+      throw new IllegalArgumentException("if keyStoreType is " + P11KEYSTORE + ", then keyStore must be " + NONE);
+    }
+
+    KeyStore ks = null;
+    String defaultKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
+    char[] passwd = defaultKeyStorePassword.isEmpty() ? null : defaultKeyStorePassword.toCharArray();
+
+    /**
+     * Try to initialize key store.
+     */
+    if (!defaultKeyStoreType.isEmpty()) {
+      logDebug("init keystore");
+      if (defaultKeyStoreProvider.isEmpty()) {
+        ks = KeyStore.getInstance(defaultKeyStoreType);
+      } else {
+        ks = KeyStore.getInstance(defaultKeyStoreType, defaultKeyStoreProvider);
+      }
+      if (!defaultKeyStore.isEmpty() && !NONE.equals(defaultKeyStore)) {
+        try (FileInputStream fs = new FileInputStream(defaultKeyStore)) {
+          ks.load(fs, passwd);
+        }
+      } else {
+        ks.load(null, passwd);
+      }
+    }
+
+    /*
+     * Try to initialize key manager.
+     */
+    logDebug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
+    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+    if (P11KEYSTORE.equals(defaultKeyStoreType)) {
+      // do not pass key passwd if using token
+      kmf.init(ks, null);
+    } else {
+      kmf.init(ks, passwd);
+    }
+
+    return kmf.getKeyManagers();
+  }
+
   @Override
   public String baseUrl() {
     return baseUrl.url().toExternalForm();