From 011a9f5a8775fad7400e476577fa4527c234e15a Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Wed, 31 Aug 2016 23:24:03 +0200 Subject: [PATCH] SONAR-8012 Restore support of client side certificates --- it/it-tests/pom.xml | 24 +++ .../src/test/java/it/Category3Suite.java | 2 + .../src/test/java/it/analysis/SSLTest.java | 196 ++++++++++++++++++ .../test/resources/analysis/SSLTest/README | 184 ++++++++++++++++ .../analysis/SSLTest/clientkeystore.jks | Bin 0 -> 2260 bytes .../analysis/SSLTest/clienttruststore.jks | Bin 0 -> 971 bytes .../resources/analysis/SSLTest/openssl.cnf | 38 ++++ .../analysis/SSLTest/serverkeystore.jks | Bin 0 -> 2258 bytes .../analysis/SSLTest/servertruststore.jks | Bin 0 -> 1109 bytes .../sonarqube/ws/client/HttpConnector.java | 110 ++++++++++ 10 files changed, 554 insertions(+) create mode 100644 it/it-tests/src/test/java/it/analysis/SSLTest.java create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/README create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/clientkeystore.jks create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/clienttruststore.jks create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/openssl.cnf create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/serverkeystore.jks create mode 100644 it/it-tests/src/test/resources/analysis/SSLTest/servertruststore.jks diff --git a/it/it-tests/pom.xml b/it/it-tests/pom.xml index f13d10e223c..ffd2d819fc5 100644 --- a/it/it-tests/pom.xml +++ b/it/it-tests/pom.xml @@ -14,6 +14,7 @@ * false + 9.3.11.v20160721 @@ -114,6 +115,29 @@ subethasmtp 3.1.6 + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.eclipse.jetty + jetty-proxy + ${jetty.version} + + + org.sonarsource.scanner.cli + sonar-scanner-cli + 2.7 + zip + diff --git a/it/it-tests/src/test/java/it/Category3Suite.java b/it/it-tests/src/test/java/it/Category3Suite.java index 9a4d76d0ed3..97899f88936 100644 --- a/it/it-tests/src/test/java/it/Category3Suite.java +++ b/it/it-tests/src/test/java/it/Category3Suite.java @@ -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 index 00000000000..098cfa5b673 --- /dev/null +++ b/it/it-tests/src/test/java/it/analysis/SSLTest.java @@ -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 index 00000000000..fb535a907c6 --- /dev/null +++ b/it/it-tests/src/test/resources/analysis/SSLTest/README @@ -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 index 0000000000000000000000000000000000000000..653f3c7f093b4bd44cee52865e622426711bfccb GIT binary patch literal 2260 zcmbVNX*kpi7oY!Zj4fLj>m*t*{KsVM%96$!*|!jL8CfzjVurRMLZpUEyS=;;V~HZ% z$d)W&S_s)9G^sF`GFQFthx_^caGvL!-#O%AA=6o&z}|Dwx_`=A z1}!yGfX~fooS{vlF@j$${ov;P>R>9pwcjCIj*PneqO?zHgG4otABf}BQ%=&l=It#U9JWD_;W^b_ja7)D( zfv56%{17CfRbkI#-st^9#Al|q!WM)ENYK!_#^H}Y@|{z)Fq5x*l_KNg7xLX~`(H|B z3NA*kM=mPpwX!ZH92Zk7D}8ePB(IVpiJ%A7s+`MqNW==Zce3DgmPUGoou#;=iNE51 z&Xr5grHLfrn?IHfAGbc%h$aw=1V-fRnj=O&dZJ8i6h~|SVk^7e+(1EGg3wS^ql95s zGMkYUYQ9yofO8ys;#^pmGFxOYI6rSFP|k4vFdEw3Mb5h+kyLu1LDrreM2_X}G)&u`2NQ%RjK_I4ZMKG&t{|6ETJ z1icP^f?M`S@G|`K-tnk(TUV(G=)sR5!S8cm1K{8ojhmy&e`}dV8Eu*aP3mB(Z zz);N!&D}u`3w=1D6?kNcKp-=8B`)q8_Wa&;7=5NlpelWL+uovz!LwY~V$R)6Y+ST) zWeCPuYtqOgE;CWJ%!g{xK0m=O?;k6j)H3=U_4WY+QiDS8m#XRAvSfr@N;NT?^RKwV z3+WGsS$yNK2GR~>^JhEvcRcQ%YE`<7-K!~A=?r0*Y|&BaLm|cDQ#Yf`JYRl3{<@?1 z2n6TWH!wjf8@{#W;+>(fN`4`Dd#ClX-hi9JjBDJ8kudu7UBTD&>U8S?)sg6w=4-5* zXd7&H-cjY1{_?(sqWs;$c>C1sdw-Y8P)d?EZ%=vU`xsYx*QZZc6HGEUPnNEkp0U)a zYmCRpObSHwqeti~24fpbqlI-G*QR@_-!a{*rgG?@T6S3qVB$HCA^sDsoLzdro^CST{`)8*Jhk;eLkI+x1u|im zK_--z4}$_wC_n;5ktmI1V)D8u00<)>pc_sYL?K~raHJH}`WQ|E-0{m05D|2WBGbqu z3Kb^}O8$a`M1)5$J_=Unmq%F-ce?(C*zn#u8CrB6@S1PD1D~cn4 zhM+D^50BF&c*~0F5=BT-7J@Oy?2hq__zb-Gjg< zs$D3({Uil^R>b9%H%9PwNz*b4M_+IfpA(PT&0trqSvD@GTI7+KzS(~6YNXWrbLx9~ z;qTMe&y@5+b~Fe{7LB$Knj!9-#zhRMHQBA0;~%)mwE0yR-u=_~YS+cS`$yeluh$0{ zb@>-iGN3R30u)7o5x==sl7d4)2q?TgNu?cdI1CD}`aM=9b+`y9gcRIVmJg-TP8sRw&`4BTP@r~rR8U~J&bAUn z3I8`G+q^i`=V(!zxs7d=maY39KFVHX%GOP1KtAXqxH0VBjU|M5le%LHA{OaM zdE+0}s`Zpa*Ywdxe9*2@{6;)pk>71;Lm#(&cx=9Hzwn(q^D|sw8KRCjZ~*UzEmB(v zbxSNDcJhpehEG$^a_BiBbIwx>O%*u=W4cYwjoh@Ona6BA*Y%IvV)KMWOb>~+$b}V9 z)y;MJUtRh*{u>T!%B4R;(25rjUcnbjlX&JIo;@lS=-kC_sAdye-gx?39jV8^xtZ5| z%T$M(E%_k&N5me7C%z4o$0v$Q+*!7Ef9s1ZSP&&r{8HgbiEmyJrRvd$Qxip@yr1?R eR`vks_lj_pv$I!ohUAfxlg)swje1IjKl=xUpV}$_ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..2529245e1c6a9335a7de32b84c54afae6462902d GIT binary patch literal 971 zcmezO_TO6u1_mY|W(3o$xs}+T%_46G4)rUsS_49vX-P0XDJO-zLgn3))v zm>5$Gc-c6$+C196^D;7WvoaWj8gd(OvN4CUun9A{1sMt&@Pjz)JRAY}nI)O2Ma709 z20|bqE*|dS{Jg}X;QZ2}2rbz4!$SGzk*MVvb%cGS8x&KABE z!2PQDUPLSx%cswk8oG%d8rh)&MoE5gt+7uh-rmZ-?yg4t7J>P{8+OUN`tCJ~SQ3%V z98vh(Lw1Uszl~;|LdPx9Oj*;~KQlJ8hG??=QcDk*p#F1P=&BpBe_1D$Wyv3X{!CbZ zi_`b7m2v^S`es{}7wDH6ZA_}P?f+HuJX)kXqSfbc;f!~Dbv`AU#=V`H^1mypP91x5 z>EWb1={bsgq|>F;Fs4SRl7RrcElNq@=(~ zU%w=^xFk7I4;&}@Kt^&=4sw(L6B;l|7#TRc9|teq6L$3GljH8sJ=W@Kd&p`ixP3X* zB@=&`Ps4e68&8QU=h?0CFM~yRVxLGxbv>NB&1cG90gjlO4K=T>b;`Mk{d@lYLVuy< zT%CvSjI?7;InDlK_@sBGT$@wSGGX!Zl*#Q6K7Lm=k>2&=f<~`!r%gj#LHpUi4`p7i zx>t6|>O+&9PQ<4z>%W9;Ou8tN{OZi@g-^cEnEr4B_ck5ym+_tkmX##cDA1#Q|W{AOQ6z=cNcRWk$?s>9w}+-fouvAW;WD|OUv!zGqk zHC}v-XH|+`Rrq;qp71 Jqv_54SpWx*UF`q> literal 0 HcmV?d00001 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 index 00000000000..4a5148fd4d7 --- /dev/null +++ b/it/it-tests/src/test/resources/analysis/SSLTest/openssl.cnf @@ -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 index 0000000000000000000000000000000000000000..319c899278a24872af2c5b8789062f7e8b74b915 GIT binary patch literal 2258 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 RPo8Za6p8j^O|R literal 0 HcmV?d00001 diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java index 1231c6c7069..3091c40faf8 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java @@ -19,12 +19,23 @@ */ 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(); -- 2.39.5